diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ed3c36d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,172 @@ +# Strata Continuous Improvement — Claude Code Guide + +## About + +Healthcare finance analytics microservice owned by **The-Outliers** team. Helps hospitals identify cost-saving opportunities across encounter, charge code, general ledger, payroll, utilization variation, quality variation, network, and strategic opportunity domains. + +**Tech stack:** .NET 8 (C# 12) + React 17 + TypeScript 4.9 + PostgreSQL + SQL Server + Snowflake + +## Shell / repo navigation + +- Claude is launched from the repo root (`C:/git/microservices/continuousimprovement`). Do not `cd` to the repo in commands. +- Avoid compound commands that combine `cd` with pipes/redirection (e.g., `cd ... && ... 2>/dev/null | head ...`) because they trigger manual-approval warnings. +- Prefer: + - `find . ...` (when already in repo root), or + - `find "C:/git/microservices/continuousimprovement" ...` (when an explicit path is needed). +- If output limiting is needed, prefer `| head -50` without `2>/dev/null`, or run redirection in a separate step. + +## Commands + +```bash +# Backend +dotnet restore +dotnet build +dotnet test + +# Frontend (run from src/Strata.ContinuousImprovement.Web/continuousimprovement/) +npm install +npm start # Dev server +npm test # Interactive test runner +npm run test-ci # CI test runner (no watch) +``` + +## Coding Standards + +Follow `.github/copilot-instructions.md` for naming, capitalization, project organization, API routes, DTOs, and principles. Key points: +- Booleans: `is`, `has`, `can` prefix +- Methods/properties/constants: PascalCase; variables: camelCase +- No full acronyms (`EntityIdsCsv` not `EntityIdsCSV`) +- Organize by business domain, not by type +- RESTful routes: plural nouns, kebab-case +- DTOs: no `DTO` suffix, only create when restricting/combining models + +## Architecture + +### Backend (C#/.NET 8) + +| Project | Purpose | Path | +|---------|---------|------| +| **Api** | ASP.NET Core Web API host, controllers | `src/Strata.ContinuousImprovement.Api/` | +| **Biz** | All business logic, services, DI registration | `src/Strata.ContinuousImprovement.Biz/` | +| **Client** | HTTP client wrapper (`IContinuousImprovementService`) | `src/Strata.ContinuousImprovement.Client/` | +| **Service** | Windows/hosted service, Hangfire worker host | `src/Strata.ContinuousImprovement.Service/` | +| **Dashboard** | Separate web host (Startup + Program) | `src/Strata.ContinuousImprovement.Dashboard/` | +| **DecisionSupport.Biz** | Encounter query services, Snowflake queries | `src/Strata.DecisionSupport.Biz/` | +| **DecisionSupport.Models** | Shared encounter/Jazz/Snowflake DTOs | `src/Strata.DecisionSupport.Models/` | + +### Frontend (React 17 + TypeScript) + +**Root:** `src/Strata.ContinuousImprovement.Web/continuousimprovement/` + +Feature modules live directly under `src/` (not a `features/` folder): +- `charge-code/`, `encounter/`, `exploration/`, `free-form/`, `general-ledger/`, `initiatives/`, `network/`, `opportunities/`, `payroll/`, `quality-variation/`, `strategic-opportunities/`, `utilization-variation/`, `configuration/` + +Each module follows: page component + `/components` + `/hooks` + `/data` + `/utils` + +**Shared module:** `src/shared/` — components, hooks, data services, contexts, constants + +### Key Constraints +- **React 17** — do NOT use React 18 features (useId, automatic batching, etc.) +- **react-router v5** — use `Switch`/`Route`/`Redirect`, NOT v6 patterns +- **Create React App** (`react-scripts ^5.0.1`) — not Vite + +## Strata Ecosystem (Proprietary — No Public Docs) + +These packages are internal to Strata. No public documentation exists. Read source/types when needed: + +| Package | Purpose | +|---------|---------| +| `@strata/tempo` | Design system (v6.33+). Import: `@strata/tempo/lib/`. Query Tempo MCP server for docs. | +| `@strata/core` | Auth (`useOidcSession`), HTTP (`getSecureService`), URL helpers | +| `@strata/charts` | Charting library | +| `@strata/jazz` | Jazz platform integration | +| `@strata/logging` | Client-side logging | +| `@strata/navbar` | Shared navigation bar | +| `@strata/score` | Score calculations | +| `@strata/signalr` | SignalR client wrapper | +| `@strata/settings` | Settings service | +| `@strata/intl` | Internationalization | +| `Strata.CS.Jazz.*` | Backend Jazz platform libraries (DI, EF, auth, caching) | +| `Riok.Mapperly` | Compile-time object mapping source generator | +| `EasyCaching` | In-memory caching (provider: `continuousImprovement_cache`) | + +## Commit Requirements + +Format: `type(scope): subject` — enforced by `.commitlintrc.js` + +**Types:** `build`, `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore` +**Scopes:** `api`, `biz`, `client`, `dashboard`, `service`, `web`, `ds-web`, `ds-models` + +Rules: +- Subject max 72 chars, no period, no sentence/start/pascal/upper case +- Scope required (warning), lowercase only +- Body and footer must have leading blank line +- Footer: `Refs: JAZZ-XXXX` (Jira ticket reference) + +Example: +``` +feat(biz): add case type family drill-down for exploration + +Refs: JAZZ-12345 +``` + +## Testing + +### Backend (NUnit + Moq) +- Unit tests: `tests/Strata.ContinuousImprovement.*.Test.Unit/` +- Integration tests: `tests/Strata.ContinuousImprovement.*.Test.Integration/` +- Jazz EF stub: `tests/Strata.ContinuousImprovement.JazzEntityFrameworkStub/` +- Pattern: `[TestFixture]`, `[Test]`, `[SetUp]`/`[OneTimeSetUp]`, `Mock` +- Run: `dotnet test` + +### Frontend (Jest + Testing Library) +- Test files: `.test.tsx` co-located with components +- Custom render wrapper: `src/test-utils/testUtils.tsx` (wraps MemoryRouter + contexts) +- Pattern: `render()`, `screen.findByRole()`, `jest.spyOn(service, 'method')` +- Run: `npm test` (from `continuousimprovement/` dir) or `npm run test-ci` for CI + +### E2E (Playwright) +- Located in separate test infrastructure + +## Feature Flags + +Backend pattern: +```csharp +// Inject IFeatureFlagWrapper, call named convenience method or generic: +await featureFlagWrapper.IsCiBenchmarkingEnabled(); +await featureFlagWrapper.IsFeatureFlagOn(FeatureFlag.ChargeCodeBeta, clientId); +``` + +Frontend pattern: +```tsx +const { isBenchmarkingEnabled } = useFeatureFlags(); +``` + +Centralized definitions: `Biz/Utilities/FeatureFlag.cs` (enum) + `FeatureFlagLookup.cs` (key mapping) + +See `docs/FeatureFlagUsage.md` for full flag inventory. + +## Key Patterns + +- **Interface-based DI** — All services have `IXxxService` interface, registered in `Biz/Configurations/ContinuousImprovementServiceExtensions.cs` via `services.AddScoped()` +- **Primary constructor DI** (C# 12) — Services use `public class XxxService(IDep1 dep1, IDep2 dep2) : IXxxService` +- **Riok.Mapperly** — Compile-time mapping via `[Mapper]` attribute on static partial classes (in `DecisionSupport.Biz/Mapperly/`) +- **Embedded SQL resources** — `.sql` files as embedded resources, loaded via `Assembly.GetExecutingAssembly().ReadResource("QueryName.sql")` (see `Biz/Utilities/ResourceExtensions.cs`) +- **EasyCaching** — In-memory provider named `continuousImprovement_cache` +- **SignalR** — Typed hub `NotificationHub : Hub`, mapped at `/api/NotificationHub` +- **Multi-database** — PostgreSQL (CentralDbContext), SQL Server (JazzDbContext via async factory), Snowflake (SnowflakeDatabaseContext) +- **API versioning** — `[ApiVersion("1.0")]` + `[Route("api/v{api-version:apiVersion}/[controller]")]` + `SlugifyParameterTransformer` +- **Frontend data services** — `getSecureService()` from `@strata/core/lib` providing typed HTTP helpers, exported as plain objects implementing interfaces +- **Frontend state** — React Context pattern (`FeatureFlagContext`, `ConfigurationsContext`, `UserSettingsContext`) + custom hooks + +## MCP Servers + +- **Tempo** — Query `@strata/tempo` component docs, props, examples. Always check before using Tempo components. +- **Jira/Atlassian** — Fetch ticket details, search issues, manage PRs via `mcp__atlassian__*` tools. + +## Reference Docs + +- `.claude/docs/backend-patterns.md` — Biz module anatomy, DI, controllers, EF, SQL, testing +- `.claude/docs/frontend-patterns.md` — Feature modules, hooks, data services, Tempo, testing +- `.claude/docs/agent-workflow.md` — AI-assisted development pipeline and context management +- `.claude/docs/adversarial-prompts.md` — Review criteria templates diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..362adc2 --- /dev/null +++ b/CONTRIBUTING.md @@ -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: + +``` +(): + +[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 isn’t 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 you’re 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 it’s 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 don’t 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! + diff --git a/Dockerfile.api b/Dockerfile.api new file mode 100644 index 0000000..28462aa --- /dev/null +++ b/Dockerfile.api @@ -0,0 +1,284 @@ +ARG PROJECT=Strata.ContinuousImprovement +ARG DSPROJECT=Strata.DecisionSupport +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" + +RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \ + && apt-get -qq update --allow-releaseinfo-change \ + && apt-get -qq install -y \ + libc6 \ + libc6-dev \ + libgdiplus \ + libx11-dev \ + ttf-mscorefonts-installer \ + fontconfig \ + && rm -rf /var/lib/apt/lists/* \ + && ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so + +############### +# Build image # +############### +FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.sdk:8.0 AS build +ARG PROJECT +ARG DSPROJECT +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" + +RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \ + && apt-get -qq update --allow-releaseinfo-change \ + && apt-get -qq install -y \ + libc6 \ + libc6-dev \ + libgdiplus \ + libx11-dev \ + ttf-mscorefonts-installer \ + fontconfig \ + awscli \ + jq \ + && rm -rf /var/lib/apt/lists/*\ + && ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so + +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 +# ############################# +# # ContinuousImprovement Api # +# ############################# + +RUN dotnet test "tests/${PROJECT}.Api.Test.Unit/${PROJECT}.Api.Test.Unit.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/api-api-tests.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutput='/test-results/' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +RUN CREDS=$(aws sts assume-role --role-arn arn:aws:iam::809820578507:role/sdt-continuous-improvement-service-role --role-session-name "DockerBuildSession"); \ + export AWS_ACCESS_KEY_ID="$(echo $CREDS | jq -r '.Credentials.AccessKeyId')"; \ + export AWS_SECRET_ACCESS_KEY="$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')"; \ + export AWS_SESSION_TOKEN="$(echo $CREDS | jq -r '.Credentials.SessionToken')"; \ + dotnet test "tests/${PROJECT}.Api.Test.Integration/${PROJECT}.Api.Test.Integration.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/integration-tests-api.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutputFormat=opencover \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Api.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +# ############################# +# # ContinuousImprovement Biz # +# ############################# + +RUN dotnet test "/src/tests/${PROJECT}.Biz.Test.Unit/${PROJECT}.Biz.Test.Unit.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/unit-tests-biz.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +RUN CREDS=$(aws sts assume-role --role-arn arn:aws:iam::809820578507:role/sdt-continuous-improvement-service-role --role-session-name "DockerBuildSession"); \ + export AWS_ACCESS_KEY_ID="$(echo $CREDS | jq -r '.Credentials.AccessKeyId')"; \ + export AWS_SECRET_ACCESS_KEY="$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')"; \ + export AWS_SESSION_TOKEN="$(echo $CREDS | jq -r '.Credentials.SessionToken')"; \ + dotnet test "tests/${PROJECT}.Biz.Test.Integration/${PROJECT}.Biz.Test.Integration.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/integration-tests-biz.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutputFormat=opencover \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +# ################################ +# # ContinuousImprovement Client # +# ################################ + +RUN dotnet test "tests/${PROJECT}.Client.Test.Unit/${PROJECT}.Client.Test.Unit.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/unit-tests-client.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutputFormat=opencover \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Client.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +RUN CREDS=$(aws sts assume-role --role-arn arn:aws:iam::809820578507:role/sdt-continuous-improvement-service-role --role-session-name "DockerBuildSession"); \ + export AWS_ACCESS_KEY_ID="$(echo $CREDS | jq -r '.Credentials.AccessKeyId')"; \ + export AWS_SECRET_ACCESS_KEY="$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')"; \ + export AWS_SESSION_TOKEN="$(echo $CREDS | jq -r '.Credentials.SessionToken')"; \ + dotnet test "tests/${PROJECT}.Client.Test.Integration/${PROJECT}.Client.Test.Integration.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/integration-tests-client.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutputFormat=opencover \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Client.Test.Integration]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +# ################### +# # DecisionSupport # +# ################### + +RUN dotnet test "tests/${DSPROJECT}.Biz.Test.Unit/${DSPROJECT}.Biz.Test.Unit.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/unit-tests-dsbiz.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutputFormat=opencover \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +RUN CREDS=$(aws sts assume-role --role-arn arn:aws:iam::809820578507:role/sdt-continuous-improvement-service-role --role-session-name "DockerBuildSession"); \ + export AWS_ACCESS_KEY_ID="$(echo $CREDS | jq -r '.Credentials.AccessKeyId')"; \ + export AWS_SECRET_ACCESS_KEY="$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')"; \ + export AWS_SESSION_TOKEN="$(echo $CREDS | jq -r '.Credentials.SessionToken')"; \ + dotnet test "tests/${DSPROJECT}.Biz.Test.Integration/${DSPROJECT}.Biz.Test.Integration.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/integration-tests-dsbiz.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutputFormat=opencover \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +RUN mv /test-results/coverage.opencover.xml /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} + +# Pack DS Biz project +RUN dotnet pack "src/${DSPROJECT}.Biz/${DSPROJECT}.Biz.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --include-symbols \ + --include-source \ + --output /pack \ + -property:PackageVersion=${VERSION} + +# Pack DS models project +RUN dotnet pack "src/${DSPROJECT}.Models/${DSPROJECT}.Models.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --include-symbols \ + --include-source \ + --output /pack \ + -property:PackageVersion=${VERSION} + +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 mkdir -p /src/buildlogs && 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} \ No newline at end of file diff --git a/Dockerfile.dashboard b/Dockerfile.dashboard new file mode 100644 index 0000000..024e111 --- /dev/null +++ b/Dockerfile.dashboard @@ -0,0 +1,73 @@ +ARG PROJECT=Strata.ContinuousImprovement +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" + +RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \ + && apt-get update --allow-releaseinfo-change \ + && apt-get install -y \ + libc6 \ + libc6-dev \ + libgdiplus \ + libx11-dev \ + ttf-mscorefonts-installer \ + fontconfig \ + && rm -rf /var/lib/apt/lists/*\ + && ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so + +############### +# 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}.Dashboard/${PROJECT}.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}.Dashboard.dll +ENV DOTNET_ENVIRONMENT=Development +ENV ASPNETCORE_ENVIRONMENT=Development + +COPY --from=build /app /app +ENTRYPOINT dotnet ${PROJECTDLL} diff --git a/Dockerfile.hangfire b/Dockerfile.hangfire new file mode 100644 index 0000000..bb0197c --- /dev/null +++ b/Dockerfile.hangfire @@ -0,0 +1,73 @@ +ARG PROJECT=Strata.ContinuousImprovement +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" + +RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \ + && apt-get update --allow-releaseinfo-change \ + && apt-get install -y \ + libc6 \ + libc6-dev \ + libgdiplus \ + libx11-dev \ + ttf-mscorefonts-installer \ + fontconfig \ + && rm -rf /var/lib/apt/lists/*\ + && ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so + +############### +# 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}.Service/${PROJECT}.Service.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --output /app + +############### +# Final image # +############### +FROM base AS final +ARG PROJECT +WORKDIR /app +ARG PROJECT +ENV PROJECTDLL=${PROJECT}.Service.dll +ENV DOTNET_ENVIRONMENT=Development +ENV ASPNETCORE_ENVIRONMENT=Development + +COPY --from=build /app /app +ENTRYPOINT dotnet ${PROJECTDLL} diff --git a/Dockerfile.local b/Dockerfile.local new file mode 100644 index 0000000..aecdf18 --- /dev/null +++ b/Dockerfile.local @@ -0,0 +1,199 @@ +ARG PROJECT=Strata.ContinuousImprovement +ARG DSPROJECT=Strata.DecisionSupport +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" + +RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \ + && apt-get -qq update --allow-releaseinfo-change \ + && apt-get -qq install -y \ + libc6 \ + libc6-dev \ + libgdiplus \ + libx11-dev \ + ttf-mscorefonts-installer \ + fontconfig \ + && rm -rf /var/lib/apt/lists/* \ + && ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so + +############### +# Build image # +############### +FROM ecr.ops.stratanetwork.net/strata.microsoft.dotnet.sdk:8.0 AS build +ARG PROJECT +ARG DSPROJECT +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" + +RUN sed -i 's/^Components: main$/& contrib/' /etc/apt/sources.list.d/debian.sources \ + && apt-get -qq update --allow-releaseinfo-change \ + && apt-get -qq install -y \ + libc6 \ + libc6-dev \ + libgdiplus \ + libx11-dev \ + ttf-mscorefonts-installer \ + fontconfig \ + && rm -rf /var/lib/apt/lists/*\ + && ln -s /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/x86_64-linux-gnu/libdl.so + +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}.Api.Test.Unit/${PROJECT}.Api.Test.Unit.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/api-api-tests.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutput='/test-results/' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +RUN dotnet test "/src/tests/${PROJECT}.Biz.Test.Unit/${PROJECT}.Biz.Test.Unit.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/unit-tests-biz.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +RUN dotnet test "tests/${PROJECT}.Client.Test.Unit/${PROJECT}.Client.Test.Unit.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/unit-tests-client.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +RUN dotnet test "tests/${DSPROJECT}.Biz.Test.Unit/${DSPROJECT}.Biz.Test.Unit.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --verbosity=normal \ + --logger="trx;LogFileName=/test-results/unit-tests-dsbiz.trx" \ + -p:CollectCoverage=true \ + -p:CoverletOutputFormat=opencover \ + -p:CoverletOutput='/test-results/' \ + -p:MergeWith='/test-results/coverage.json' \ + -p:Exclude=\"[Strata.ContinuousImprovement.Biz.Test.Unit]*,[Strata.ContinuousImprovement.JazzEntityFrameworkStub]*\" + +RUN mv /test-results/coverage.opencover.xml /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} + +# Pack DS Biz project +RUN dotnet pack "src/${DSPROJECT}.Biz/${DSPROJECT}.Biz.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --include-symbols \ + --include-source \ + --output /pack \ + -property:PackageVersion=${VERSION} + +# Pack DS models project +RUN dotnet pack "src/${DSPROJECT}.Models/${DSPROJECT}.Models.csproj" \ + --configuration Release \ + --no-restore \ + --no-build \ + --include-symbols \ + --include-source \ + --output /pack \ + -property:PackageVersion=${VERSION} + +# /* Running locally +# 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 +# */ + +############### +# 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 + +ENTRYPOINT dotnet ${PROJECTDLL} diff --git a/Dockerfile.web b/Dockerfile.web new file mode 100644 index 0000000..e3a8a4e --- /dev/null +++ b/Dockerfile.web @@ -0,0 +1,28 @@ +ARG SONARLOGIN='' +ARG VERSION=0.0.0 + +FROM node:18.17.0 as build +ARG SONARLOGIN +ARG VERSION + +# 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.ContinuousImprovement.Web/continuousimprovement/package*.json ./ +RUN npm install --legacy-peer-deps +RUN npm ci --legacy-peer-deps + +# Copy in rest of app and build +COPY src/Strata.ContinuousImprovement.Web/continuousimprovement/ . +RUN npm run build + +# Run tests +# RUN npm run test-ci + +# Build release container +FROM nginx:alpine +COPY --from=build /app/build /usr/share/nginx/html +COPY src/Strata.ContinuousImprovement.Web/nginx.conf /etc/nginx/conf.d/default.conf diff --git a/README.md b/README.md index fb41789..2cbc50d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ -# continuousimprovement +# Strata.ContinuousImprovement +[![build](https://github.com/stratadecision/continuousimprovement/actions/workflows/build.yaml/badge.svg)](https://github.com/stratadecision/continuousimprovement/actions/workflows/build.yaml) +[![End-to-end tests](https://github.com/stratadecision/continuousimprovement/actions/workflows/e2e-testrunner.yml/badge.svg)](https://github.com/stratadecision/continuousimprovement/actions/workflows/e2e-testrunner.yml) + +## General Information +This microserivce and react UI provide access for the Continuous Improvements Opportunities +* whenever a change is made locally, i think the continuousimprovement needs to be unistalled and reinstalled + +## Credits +This microservice is developed and maintained by The-Outliers + +## install continuousimprovement +dotnet new -i C:\git\strata.continuousimprovements.microservices\continuousimprovements\strata.microservice +## unistall continuousimprovement +dotnet new -u C:\git\strata.continuousimprovements.microservices\continuousimprovements\strata.microservice + +# create new api project with postgres project from continuousimprovement +dotnet new strata.microservice -o C:\git\strata.foobar -n Strata.Foobar --api --biz --postgres + +## Branching and Versioning + +| branch | version format | example | +| ---------- | --------------------- | --------------- | +| master | #.#.# | 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://confluence.sdt.local/display/DOP/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 stratacontinuousimprovement_continuousimprovement-api +docker cp throwaway:/pack . +docker rm throwaway +``` + +### Cleanup + +``` +docker-compose down +``` diff --git a/Strata.ContinuousImprovement.sln b/Strata.ContinuousImprovement.sln new file mode 100644 index 0000000..3b20e1c --- /dev/null +++ b/Strata.ContinuousImprovement.sln @@ -0,0 +1,120 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33403.182 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Api", "src\Strata.ContinuousImprovement.Api\Strata.ContinuousImprovement.Api.csproj", "{C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Biz.Test.Unit", "tests\Strata.ContinuousImprovement.Biz.Test.Unit\Strata.ContinuousImprovement.Biz.Test.Unit.csproj", "{775E79BD-A8F5-4756-8E94-390A72018BF8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Service", "src\Strata.ContinuousImprovement.Service\Strata.ContinuousImprovement.Service.csproj", "{EB523C92-2FED-4631-A73B-17F37B742E7C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Biz", "src\Strata.ContinuousImprovement.Biz\Strata.ContinuousImprovement.Biz.csproj", "{533D0BEF-FBF2-47E2-98E7-11AE930E2E65}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Web", "src\Strata.ContinuousImprovement.Web\Strata.ContinuousImprovement.Web.csproj", "{ABCB26F8-6044-41A8-BBBE-B00E95BB585D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Dashboard", "src\Strata.ContinuousImprovement.Dashboard\Strata.ContinuousImprovement.Dashboard.csproj", "{51D04755-17C4-49CF-944B-D4347E7C550F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Client", "src\Strata.ContinuousImprovement.Client\Strata.ContinuousImprovement.Client.csproj", "{EDBE4F6B-46D0-4332-9A38-7F2C011AA490}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Api.Test.Unit", "tests\Strata.ContinuousImprovement.Api.Test.Unit\Strata.ContinuousImprovement.Api.Test.Unit.csproj", "{DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.JazzEntityFrameworkStub", "tests\Strata.ContinuousImprovement.JazzEntityFrameworkStub\Strata.ContinuousImprovement.JazzEntityFrameworkStub.csproj", "{D04952E8-BDF1-44C6-819C-A066BADE165B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.DecisionSupport.Biz", "src\Strata.DecisionSupport.Biz\Strata.DecisionSupport.Biz.csproj", "{12F6978F-C62B-4268-8B7B-72887B53C223}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.DecisionSupport.Biz.Test.Unit", "tests\Strata.DecisionSupport.Biz.Test.Unit\Strata.DecisionSupport.Biz.Test.Unit.csproj", "{EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.DecisionSupport.Models", "src\Strata.DecisionSupport.Models\Strata.DecisionSupport.Models.csproj", "{7743C7CC-91E6-4347-95EC-F18B2F173291}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strata.ContinuousImprovement.Client.Test.Unit", "tests\Strata.ContinuousImprovement.Client.Test.Unit\Strata.ContinuousImprovement.Client.Test.Unit.csproj", "{F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.DecisionSupport.Biz.Test.Integration", "tests\Strata.DecisionSupport.Biz.Test.Integration\Strata.DecisionSupport.Biz.Test.Integration.csproj", "{0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.ContinuousImprovement.Biz.Test.Integration", "tests\Strata.ContinuousImprovement.Biz.Test.Integration\Strata.ContinuousImprovement.Biz.Test.Integration.csproj", "{EFC6B15D-C22B-480B-9E50-9BBAA33A9030}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.ContinuousImprovement.Client.Test.Integration", "tests\Strata.ContinuousImprovement.Client.Test.Integration\Strata.ContinuousImprovement.Client.Test.Integration.csproj", "{4515A4CD-906B-4204-86A4-5DE5BCE43E1D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.ContinuousImprovement.Api.Test.Integration", "tests\Strata.ContinuousImprovement.Api.Test.Integration\Strata.ContinuousImprovement.Api.Test.Integration.csproj", "{A730D5B3-50A1-5419-8945-E1C70294B5B0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4CD8DC4-3B88-4BA6-9F78-C641D365C2EE}.Release|Any CPU.Build.0 = Release|Any CPU + {775E79BD-A8F5-4756-8E94-390A72018BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {775E79BD-A8F5-4756-8E94-390A72018BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {775E79BD-A8F5-4756-8E94-390A72018BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {775E79BD-A8F5-4756-8E94-390A72018BF8}.Release|Any CPU.Build.0 = Release|Any CPU + {EB523C92-2FED-4631-A73B-17F37B742E7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB523C92-2FED-4631-A73B-17F37B742E7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB523C92-2FED-4631-A73B-17F37B742E7C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB523C92-2FED-4631-A73B-17F37B742E7C}.Release|Any CPU.Build.0 = Release|Any CPU + {533D0BEF-FBF2-47E2-98E7-11AE930E2E65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {533D0BEF-FBF2-47E2-98E7-11AE930E2E65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {533D0BEF-FBF2-47E2-98E7-11AE930E2E65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {533D0BEF-FBF2-47E2-98E7-11AE930E2E65}.Release|Any CPU.Build.0 = Release|Any CPU + {ABCB26F8-6044-41A8-BBBE-B00E95BB585D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABCB26F8-6044-41A8-BBBE-B00E95BB585D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABCB26F8-6044-41A8-BBBE-B00E95BB585D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABCB26F8-6044-41A8-BBBE-B00E95BB585D}.Release|Any CPU.Build.0 = Release|Any CPU + {51D04755-17C4-49CF-944B-D4347E7C550F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51D04755-17C4-49CF-944B-D4347E7C550F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51D04755-17C4-49CF-944B-D4347E7C550F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51D04755-17C4-49CF-944B-D4347E7C550F}.Release|Any CPU.Build.0 = Release|Any CPU + {EDBE4F6B-46D0-4332-9A38-7F2C011AA490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDBE4F6B-46D0-4332-9A38-7F2C011AA490}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDBE4F6B-46D0-4332-9A38-7F2C011AA490}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDBE4F6B-46D0-4332-9A38-7F2C011AA490}.Release|Any CPU.Build.0 = Release|Any CPU + {DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA1AE588-5B04-43CA-BA0C-9DF8F737C97A}.Release|Any CPU.Build.0 = Release|Any CPU + {D04952E8-BDF1-44C6-819C-A066BADE165B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D04952E8-BDF1-44C6-819C-A066BADE165B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D04952E8-BDF1-44C6-819C-A066BADE165B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D04952E8-BDF1-44C6-819C-A066BADE165B}.Release|Any CPU.Build.0 = Release|Any CPU + {12F6978F-C62B-4268-8B7B-72887B53C223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12F6978F-C62B-4268-8B7B-72887B53C223}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12F6978F-C62B-4268-8B7B-72887B53C223}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12F6978F-C62B-4268-8B7B-72887B53C223}.Release|Any CPU.Build.0 = Release|Any CPU + {EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EAB9B29E-CAA6-4974-A8D5-D0AAF3FF2B0C}.Release|Any CPU.Build.0 = Release|Any CPU + {7743C7CC-91E6-4347-95EC-F18B2F173291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7743C7CC-91E6-4347-95EC-F18B2F173291}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7743C7CC-91E6-4347-95EC-F18B2F173291}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7743C7CC-91E6-4347-95EC-F18B2F173291}.Release|Any CPU.Build.0 = Release|Any CPU + {F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4FCAF36-CDD7-4341-8546-5BF6DD8501CE}.Release|Any CPU.Build.0 = Release|Any CPU + {0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DE4A5ED-BA94-40AD-9553-3C4312DCC86C}.Release|Any CPU.Build.0 = Release|Any CPU + {EFC6B15D-C22B-480B-9E50-9BBAA33A9030}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EFC6B15D-C22B-480B-9E50-9BBAA33A9030}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EFC6B15D-C22B-480B-9E50-9BBAA33A9030}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EFC6B15D-C22B-480B-9E50-9BBAA33A9030}.Release|Any CPU.Build.0 = Release|Any CPU + {4515A4CD-906B-4204-86A4-5DE5BCE43E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4515A4CD-906B-4204-86A4-5DE5BCE43E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4515A4CD-906B-4204-86A4-5DE5BCE43E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4515A4CD-906B-4204-86A4-5DE5BCE43E1D}.Release|Any CPU.Build.0 = Release|Any CPU + {A730D5B3-50A1-5419-8945-E1C70294B5B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A730D5B3-50A1-5419-8945-E1C70294B5B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A730D5B3-50A1-5419-8945-E1C70294B5B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A730D5B3-50A1-5419-8945-E1C70294B5B0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F6CD834D-51B1-4B10-9DE1-DFE5A16D4177} + EndGlobalSection +EndGlobal diff --git a/Strata.ContinuousImprovement.sln.DotSettings b/Strata.ContinuousImprovement.sln.DotSettings new file mode 100644 index 0000000..82ab1f3 --- /dev/null +++ b/Strata.ContinuousImprovement.sln.DotSettings @@ -0,0 +1,2 @@ + + <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Strata.ContinuousImprovement.Biz" ModuleVersionMask="*" ClassMask="Microsoft.Extensions.DependencyInjection.*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> \ No newline at end of file diff --git a/docs/FeatureFlagUsage.md b/docs/FeatureFlagUsage.md new file mode 100644 index 0000000..14f4aa2 --- /dev/null +++ b/docs/FeatureFlagUsage.md @@ -0,0 +1,347 @@ +# Feature Flag Usage Documentation - IFeatureFlagServiceClient.IsEnabledAsync + +This document provides a comprehensive overview of all feature flags used throughout the Continuous Improvement codebase via the `IFeatureFlagServiceClient.IsEnabledAsync` method. + +## Overview + +The application uses feature flags to control functionality on a per-client basis in this multi-tenant system. Feature flags are checked using the `IFeatureFlagServiceClient` service and are typically scoped to a specific database GUID. + +### Feature Flag Index + +| # | Feature Flag | Description | Section | +|---|--------------|-------------|---------| +| 1 | `ciflexibleexplorationenabled` | Controls access to flexible exploration features for non-SDT employees | [Section 1](#1-ciflexibleexplorationenabled) | +| 2 | `enableencounteropportunityintempo` | Controls whether encounter opportunities are enabled in the Tempo interface | [Section 2](#2-enableencounteropportunityintempo) | +| 3 | `ciflexibleexplorationpage3enabled` | Controls access to the flexible exploration information page (third page) | [Section 3](#3-ciflexibleexplorationpage3enabled) | +| 4 | `enablepayrollopportunityintempo` | Controls whether payroll opportunities are enabled in the Tempo interface | [Section 4](#4-enablepayrollopportunityintempo) | +| 5 | `chargecodebeta` | Controls access to charge code beta features | [Section 5](#5-chargecodebeta) | +| 6 | `cibenchmarkingenabled` | Controls benchmarking features in continuous improvement | [Section 6](#6-cibenchmarkingenabled) | +| 7 | `ciopportunityworkbooks` | Controls opportunity workbook features | [Section 7](#7-ciopportunityworkbooks) | +| 8 | `networkopportunitiesenabled` | Controls network opportunity features | [Section 8](#8-networkopportunitiesenabled) | +| 9 | `strategicopportunityforchargecode` | Controls strategic opportunity features specifically for charge codes | [Section 9](#9-strategicopportunityforchargecode) | +| 10 | `ciexplorationpopulationmanagementlaunchesstrategicopportunitywizard` | Controls if the Add Opportunity button on the Exploration Population page launches the Strategic Opportunity Wizard | +## Feature Flag Keys and Usage + +### 1. "ciflexibleexplorationenabled" +**Purpose**: Controls access to flexible exploration features for non-SDT employees + +**Usage Locations**: +- `AllOpportunitiesController.cs` (line 40) +- `ExplorationOpportunitiesController.cs` (line 45) + +**Implementation Pattern**: +```csharp +// C# example +public async Task SomeAction() +{ + if (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationenabled")) + { + // Code for flexible exploration features + } + else + { + // Fallback or default behavior + } +} +``` + +**Context**: Enables flexible exploration functionality. SDT employees always have access, while client users need this feature flag enabled. + +--- + +### 2. "enableencounteropportunityintempo" +**Purpose**: Controls whether encounter opportunities are enabled in the Tempo interface + +**Usage Locations**: +- `EncounterOpportunitiesController.cs` (lines 90, 175) + +**Implementation Pattern**: +```csharp +// C# example +public async Task TempoAction() +{ + if (await _featureFlagServiceClient.IsEnabledAsync("enableencounteropportunityintempo")) + { + // Code for encounter opportunities in Tempo + } + else + { + // Fallback to Jazz interface or show message + } +} +``` + +**Context**: Feature flag to enable encounter opportunity functionality in the modern Tempo interface versus legacy Jazz interface. + +--- + +### 3. "ciflexibleexplorationpage3enabled" +**Purpose**: Controls access to the flexible exploration information page (third page) + +**Usage Locations**: +- `ExplorationController.cs` (line 45) + +**Implementation Pattern**: +```csharp +// C# example +public async Task ExplorationPageAction() +{ + if (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationpage3enabled")) + { + // Code for the third page of flexible exploration + } + else + { + // Redirect or show less data + } +} +``` + +**Context**: Enables the third page of flexible exploration functionality for enhanced data exploration capabilities. + +--- + +### 4. "enablepayrollopportunityintempo" +**Purpose**: Controls whether payroll opportunities are enabled in the Tempo interface + +**Usage Locations**: +- `PayrollOpportunitiesController.cs` (line 85) + +**Implementation Pattern**: +```csharp +// C# example +public async Task PayrollTempoAction() +{ + if (await _featureFlagServiceClient.IsEnabledAsync("enablepayrollopportunityintempo")) + { + // Code for payroll opportunities in Tempo + } + else + { + // Fallback to other methods or show message + } +} +``` + +**Context**: Feature flag to enable payroll opportunity functionality in the modern Tempo interface. + +--- + +### 5. "chargecodebeta" +**Purpose**: Controls access to charge code beta features + +**Usage Locations**: +- `ChargeCodeOpportunitiesController.cs` (line 243) + +**Implementation Pattern**: +```csharp +// C# example +public async Task ChargeCodeBetaAction() +{ + if (await _featureFlagServiceClient.IsEnabledAsync("chargecodebeta")) + { + // Code for charge code beta features + } + else + { + // Hide or disable beta features + } +} +``` + +**Context**: Beta feature flag for charge code opportunities functionality, controlling access to new charge code features. + +--- + +### 6. "cibenchmarkingenabled" +**Purpose**: Controls benchmarking features in continuous improvement + +**Usage Locations**: +- Frontend TypeScript components +- Configuration and peer group functionality + +**Implementation Pattern**: +```typescript +// TypeScript example +if (featureFlagService.isEnabled("cibenchmarkingenabled")) { + // Code for benchmarking features +} else { + // Alternative code path +} +``` + +**Context**: Enables benchmarking functionality in the CI application, allowing comparison against peer groups. + +--- + +### 7. "ciopportunityworkbooks" +**Purpose**: Controls opportunity workbook features + +**Usage Locations**: +- Referenced in TypeScript interface definitions +- Workbook management components + +**Context**: Feature flag for workbook functionality related to opportunities. + +--- + +### 8. "networkopportunitiesenabled" +**Purpose**: Controls network opportunity features + +**Usage Locations**: +- Frontend navigation and components +- Network opportunity controllers + +**Implementation Pattern**: +```typescript +// TypeScript example +if (featureFlagService.isEnabled("networkopportunitiesenabled")) { + // Code for network opportunity features +} else { + // Code for standard opportunities +} +``` + +**Context**: Enables network opportunity functionality for multi-client opportunity management. + +--- + +### 9. "strategicopportunityforchargecode" +**Purpose**: Controls strategic opportunity features specifically for charge codes + +**Usage Locations**: +- Frontend configuration components +- Strategic opportunity management + +**Implementation Pattern**: +```typescript +// TypeScript example +if (featureFlagService.isEnabled("strategicopportunityforchargecode")) { + // Code for strategic opportunities for charge codes +} else { + // Fallback code +} +``` + +**Context**: Enables strategic opportunity functionality specifically for charge code-based opportunities. + +--- + +### 10. "ciexplorationpopulationmanagementlaunchesstrategicopportunitywizard" +**Purpose**: Controls strategic opportunity features specifically for charge codes + +**Usage Locations**: +- Frontend configuration components +- Strategic opportunity management + +**Implementation Pattern**: +```typescript +// TypeScript example +if (featureFlagService.isEnabled("ciexplorationpopulationmanagementlaunchesstrategicopportunitywizard")) { + // Code to launch strategic opportunities wizard +} else { + // Fallback code +} +``` + +**Context**: Enables the Add Opportunity button on the Exploration Population page to launch the Strategic Opportunity wizard. + + +## Key Controllers Using Feature Flags + +| Controller | Feature Flags Used | Purpose | +|------------|-------------------|---------| +| `AllOpportunitiesController` | `ciflexibleexplorationenabled` | Flexible exploration access control | +| `ExplorationOpportunitiesController` | `ciflexibleexplorationenabled` | Exploration features | +| `ExplorationController` | `ciflexibleexplorationpage3enabled` | Advanced exploration pages | +| `EncounterOpportunitiesController` | `enableencounteropportunityintempo` | Tempo interface integration | +| `PayrollOpportunitiesController` | `enablepayrollopportunityintempo` | Payroll Tempo integration | +| `ChargeCodeOpportunitiesController` | `chargecodebeta` | Beta charge code features | +| `SettingsController` | All CI area flags | Global feature management | + +## Frontend Integration + +The frontend TypeScript code integrates with feature flags through: + +- **Navigation Control**: Menu items visibility based on feature flags +- **Component Rendering**: Conditional rendering of UI components +- **Service Availability**: API service availability checks +- **User Interface**: Feature-specific UI elements and workflows + +### Example Frontend Usage +```typescript +// TypeScript example +async function loadOpportunities() { + if (await featureFlagService.isEnabled("ciflexibleexplorationenabled")) { + // Load flexible exploration opportunities + } else { + // Load standard opportunities + } +} +``` + +## Database Scope + +All feature flags are scoped to specific database GUIDs, enabling per-client feature control in the multi-tenant architecture. This allows different clients to have different features enabled based on their subscription level or beta participation. + +## Testing Considerations + +When testing feature flag functionality: + +1. **Mock the `IFeatureFlagServiceClient`** in unit tests +2. **Test both enabled and disabled states** for each feature +3. **Verify SDT employee bypass logic** where applicable +4. **Test database GUID scoping** for multi-tenant scenarios + +### Example Test Mock +```csharp +// C# example using Moq +var featureFlagMock = new Mock(); +featureFlagMock.Setup(ff => ff.IsEnabledAsync("ciflexibleexplorationenabled")) + .ReturnsAsync(true); + +// Use featureFlagMock.Object in tests +``` + +## Service Registration + +Feature flags are registered in the dependency injection container via the `IFeatureFlagServiceClient` service, typically configured in the `Startup.cs` or `Program.cs` file for the application. + +## Logging and Monitoring + +Many feature flag checks include structured logging to track usage patterns: + +```csharp +// C# example +if (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationenabled")) +{ + _logger.LogInformation("Flexible exploration feature enabled"); +} +else +{ + _logger.LogInformation("Flexible exploration feature disabled"); +} +``` + +## Maintenance Notes + +- All feature flag keys should be documented when added +- Consider feature flag lifecycle and removal strategy for permanent features +- Monitor feature flag usage through logging for analysis +- Ensure consistent naming conventions for new feature flags +- Regularly review and clean up obsolete feature flags +- Update this documentation when new feature flags are added + +## Feature Flag Lifecycle + +1. **Development**: New feature flags are added for experimental or beta features +2. **Testing**: Features are tested with flags enabled/disabled +3. **Rollout**: Gradual enabling of features for specific clients +4. **Stabilization**: Features become stable and widely adopted +5. **Cleanup**: Feature flags are removed once features are permanent + +--- + +*Last Updated: November 2025* +*Document Version: 1.0* +*Total Feature Flags Documented: 9* \ No newline at end of file diff --git a/docs/add-opportunity-type-playbook.md b/docs/add-opportunity-type-playbook.md new file mode 100644 index 0000000..a889251 --- /dev/null +++ b/docs/add-opportunity-type-playbook.md @@ -0,0 +1,602 @@ +# Add Opportunity Type Playbook + +Single source of truth for adding a new strategic opportunity type. Derived from the Charge Code (CC) and General Ledger (GL) implementations. + +## Quick-Scan Checklist + +Use this to track progress. Each item references a detailed section below. + +### Backend +- [ ] [Entity](#1-entity) — `StrategicOpportunity{Type}.cs` (TPT child table) +- [ ] [Enums](#2-enums) — `OpportunityTypes` value + `MeasureType` values +- [ ] [DTOs](#3-dtos) — Create DTO + Update DTO +- [ ] [Detail Service](#4-detail-service) — `IStrategic{Type}OpportunityDetailService` + implementation +- [ ] [Query Layer](#5-query-layer) — SQL files, ServiceQuery, QueryBuilder +- [ ] [Distribution Process](#6-distribution-process) — `DistributionProcess{Type}.cs` + SQL builder +- [ ] [Dimension Config](#7-dimension-config) — `{Type}DimensionsConfig` in `StrategicOpportunityService` +- [ ] [Service Methods](#8-service-methods) — CRUD on `IStrategicOpportunityService` +- [ ] [Controller Endpoints](#9-controller-endpoints) — REST endpoints +- [ ] [DI Registration](#10-di-registration) — `ContinuousImprovementServiceExtensions.cs` +- [ ] [Migration](#11-database-migration) — EF Core migration for child table + +### Frontend +- [ ] [Data Model](#f1-data-model) — `IStrategicOpportunity{Type}Data.ts` +- [ ] [Measure Types](#f2-measure-types) — enum values + display names in `MeasureType.ts` +- [ ] [Strategy Hook](#f3-strategy-hook) — `use{Type}Strategy.ts` implementing `IOpportunityTypeStrategy` +- [ ] [Define Component](#f4-define-component) — `{Type}Define.tsx` +- [ ] [Detail Store](#f5-detail-store) — `StrategicOpportunity{Type}DetailStoreTreeData.ts` +- [ ] [Detail Grid](#f6-detail-grid) — `StrategicOpportunity{Type}DetailGrid.tsx` +- [ ] [Layout Config](#f7-layout-config) — entry in `IStrategicOpportunityLayout.ts` +- [ ] [Dimension Enums](#f8-dimension-enums) — fields/sections/columns in `StrategicOpportunityDetailDimension.ts` +- [ ] [Service Methods](#f9-service-methods) — HTTP calls in `strategicOpportunityService.tsx` +- [ ] [Wizard Integration](#f10-wizard-integration) — hook call + strategy switch in `StrategicOpportunity.tsx` +- [ ] [Routes](#f11-routes) — create/edit routes in `Navigation.tsx` +- [ ] [List Page](#f12-list-page) — ButtonMenu option in `StrategicItems.tsx` + +### Cross-Cutting +- [ ] [OpportunityTypes enum](#x1-opportunitytypes-enum) — both backend + frontend must match +- [ ] [Detail Page Integration](#x2-detail-page-integration) — `StrategicOpportunityDetailPage.tsx` branches +- [ ] [Tests](#x3-testing) + +--- + +## Backend Steps + +### 1. Entity + +Create `Biz/StrategicOpportunities/StrategicOpportunity{Type}.cs` inheriting from `StrategicOpportunity` (TPT pattern). + +**Pattern:** +```csharp +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Table("strategic_opportunity_{type}")] + public class StrategicOpportunity{Type} : StrategicOpportunity + { + // Type-specific properties + public string SomeHPath { get; set; } + + public StrategicOpportunity{Type}() { } + + // Copy constructor (used by CopyStrategicOpportunity) + public StrategicOpportunity{Type}(StrategicOpportunity{Type} opportunity, string opportunityName) + { + // Copy all base properties + Name = opportunityName; + OpportunityType = opportunity.OpportunityType; + OpportunityFiltersJSON = opportunity.OpportunityFiltersJSON; + Rollup1Id = opportunity.Rollup1Id; + Rollup2Id = opportunity.Rollup2Id; + Rollup3Id = opportunity.Rollup3Id; + BaselineType = opportunity.BaselineType; + BaselineDistribution = opportunity.BaselineDistribution; + BaselineStartDate = opportunity.BaselineStartDate; + BaselineEndDate = opportunity.BaselineEndDate; + EstimatedTrackingDuration = opportunity.EstimatedTrackingDuration; + StrataId = opportunity.StrataId; + LayoutJSON = opportunity.LayoutJSON; + TeamMembers = opportunity.TeamMembers; + OwnerGuid = opportunity.OwnerGuid; + ExecutiveSponsorGuid = opportunity.ExecutiveSponsorGuid; + CostLeaderGuid = opportunity.CostLeaderGuid; + + // Copy type-specific properties + SomeHPath = opportunity.SomeHPath; + } + } +} +``` + +**DbContext:** Add `DbSet` to `CentralDbContext` and configure in `OnModelCreating`. + +**References:** +- `Biz/StrategicOpportunities/StrategicOpportunityChargeCode.cs` +- `Biz/StrategicOpportunities/StrategicOpportunityGL.cs` +- Base: `Biz/StrategicOpportunities/StrategicOpportunity.cs` + +### 2. Enums + +**OpportunityTypes** (`Biz/Generics/OpportunityTypes.cs` or wherever defined): +Add a new enum value. Current values: +- `StrategicOpportunityChargeCode` (value in `IAllOpportunity.ts` frontend) +- `StrategicOpportunityGL` + +**MeasureType** (`Biz/StrategicOpportunities/Enums/MeasureType.cs`): +Add new measure type values after the last GL value (currently 37 = `GLMarginPerAdjDischarge`). +```csharp +// {Type} measure types (38+) +{Type}Measure1 = 38, +{Type}Measure2, +// ... +``` + +Also add a `{TYPE}_MEASURE_TYPES` constant list in `MeasureTypeConstants`. + +**Other enums:** If the type has a concept like GL's `GLAccountType`, create a new enum file in `Biz/StrategicOpportunities/Enums/`. + +### 3. DTOs + +Create two DTOs in `Biz/StrategicOpportunities/Dtos/`: + +**Create DTO** — `StrategicOpportunity{Type}Dto.cs`: +```csharp +public class StrategicOpportunity{Type}Dto : StrategicOpportunityDto +{ + // Type-specific properties matching the entity + public string SomeHPath { get; set; } +} +``` + +**Update DTO** — `StrategicOpportunity{Type}UpdateDto.cs`: +```csharp +public class StrategicOpportunity{Type}UpdateDto +{ + public StrategicOpportunity{Type}Dto Opportunity { get; set; } + public bool ShouldPopulateOpportunityDetails { get; set; } +} +``` + +**References:** +- `Dtos/StrategicOpportunityGLDto.cs` / `StrategicOpportunityGLUpdateDto.cs` +- `Dtos/StrategicOpportunityChargeCodeDto.cs` / `StrategicOpportunityChargeCodeUpdateDto.cs` + +### 4. Detail Service + +Create interface + implementation for populating detail data. + +**Interface** — `IStrategic{Type}OpportunityDetailService.cs`: +```csharp +public interface IStrategic{Type}OpportunityDetailService +{ + Task PopulateOpportunityDetailsAsync(StrategicOpportunity{Type} opportunity, CancellationToken ct); + Task GetDetailDataAsync(long opportunityId, bool isTracking, List dimensions, CancellationToken ct); +} +``` + +**Implementation** — `Strategic{Type}OpportunityDetailService.cs`: +Uses primary constructor DI, queries Snowflake/SQL for dimension data, populates `StrategicOpportunityDetail` rows. + +**References:** +- `IStrategicChargeCodeOpportunityDetailService.cs` + `StrategicChargeCodeOpportunityDetailService.cs` +- `IStrategicGLOpportunityDetailService.cs` + `StrategicGLOpportunityDetailService.cs` + +### 5. Query Layer + +**SQL files** (embedded resources in `Biz/StrategicOpportunities/Queries/` or similar): +- `StrategicOpportunity{Type}DetailCTE.sql` — main detail data query +- Dimension-specific queries as needed + +**ServiceQuery** — `StrategicOpportunity{Type}ServiceQuery.cs`: +Loads SQL from embedded resources, provides query text to the detail service. + +**QueryBuilder** — `StrategicOpportunity{Type}DetailQueryBuilder.cs`: +Implements `IStrategicOpportunityDetailQueryBuilder`, builds parameterized queries for dimensions. + +**References:** +- `Queries/StrategicOpportunityGLDetailQueryBuilder.cs` +- `Queries/StrategicOpportunityGLServiceQuery.cs` +- `StrategicOpportunityServiceQuery.cs` (CC version) + +### 6. Distribution Process + +Create in `Biz/StrategicOpportunities/DistributionProcess/{Type}/`: + +- `DistributionProcess{Type}.cs` — orchestrates data distribution +- `DistributionProcess{Type}SqlBuilder.cs` — builds SQL for distribution +- `Distribution{Type}Summary.cs` — summary model + +Update `DistributionProcessFactory.cs` to handle the new type. + +**References:** +- `DistributionProcess/ChargeCode/DistributionProcessChargeCode.cs` +- `DistributionProcess/GL/DistributionProcessGL.cs` +- `DistributionProcess/DistributionProcessFactory.cs` + +### 7. Dimension Config + +In `StrategicOpportunityService.cs`, add a `{Type}DimensionsConfig` that maps dimension IDs to dimension names/hierarchy paths for the new type. + +### 8. Service Methods + +Add to `IStrategicOpportunityService.cs`: +```csharp +Task CreateOpportunity{Type}Async(StrategicOpportunity{Type}Dto dto, CancellationToken ct); +Task UpdateOpportunity{Type}(long id, StrategicOpportunity{Type}UpdateDto dto, CancellationToken ct); +Task GetStrategicOpportunity{Type}(long id, CancellationToken ct); +Task Delete{Type}OpportunityAsync(long id, CancellationToken ct); +``` + +Implement in `StrategicOpportunityService.cs`. + +### 9. Controller Endpoints + +Add endpoints to the strategic opportunities controller (or create a new controller if separation is needed): +``` +POST /api/v1.0/strategic-opportunities/{type} — Create +PUT /api/v1.0/strategic-opportunities/{type}/{id} — Update +GET /api/v1.0/strategic-opportunities/{id}/{type} — Get for edit +DELETE /api/v1.0/strategic-opportunities/{type}/{id} — Delete +POST /api/v1.0/strategic-opportunities/{id}/{type}-detail — Get detail data +PUT /api/v1.0/strategic-opportunities/{id}/{type}-details — Update goal values +``` + +### 10. DI Registration + +In `Biz/Configurations/ContinuousImprovementServiceExtensions.cs`: +```csharp +services.AddScoped(); +``` + +### 11. Database Migration + +```bash +dotnet ef migrations add AddStrategicOpportunity{Type}Table -p src/Strata.ContinuousImprovement.Biz -s src/Strata.ContinuousImprovement.Api +``` + +**TPT gotcha:** EF Core generates the child table PK with the same name as the parent (`pk_strategic_opportunity`). You must manually rename it in the migration file: +```csharp +// Change this: +name: "pk_strategic_opportunity", +// To this: +name: "pk_strategic_opportunity_{type}", +``` + +--- + +## Frontend Steps + +All paths relative to `src/Strata.ContinuousImprovement.Web/continuousimprovement/src/`. + +### F1. Data Model + +Create `strategic-opportunities/data/IStrategicOpportunity{Type}Data.ts`: +```typescript +import { IStrategicOpportunityData, defaultStrategicOpportunityData } from './IStrategicOpportunityData'; + +export interface IStrategicOpportunity{Type}Data extends IStrategicOpportunityData { + // Type-specific properties + someHPath: string; +} + +export const defaultStrategicOpportunity{Type}Data: IStrategicOpportunity{Type}Data = { + ...defaultStrategicOpportunityData, + someHPath: '' +}; +``` + +**References:** +- `data/IStrategicOpportunityChargeCodeData.ts` +- `data/IStrategicOpportunityGLData.ts` + +### F2. Measure Types + +In `strategic-opportunities/data/MeasureType.ts`: + +1. Add enum values (must match backend `MeasureType.cs`): +```typescript +// {Type} measure types (sync with backend, values N-M) +{Type}Measure1 = N, +{Type}Measure2, +``` + +2. Add display names array: +```typescript +export const {type}MeasureTypeData: IKeyNameItem[] = [ + { key: MeasureType.{Type}Measure1, name: 'Display Name 1' }, + // ... +]; +``` + +3. Add entries to `mapMeasureTypeToDesiredResult`. + +4. Add helper like `is{Type}PerMeasureType()` if the type has "per unit" style measures. + +### F3. Strategy Hook + +Create `strategic-opportunities/wizard/{type}/use{Type}Strategy.ts`: + +Must implement `IOpportunityTypeStrategy` from `wizard/types/IOpportunityTypeStrategy.ts`: + +```typescript +export interface IOpportunityTypeStrategy { + getDefaultData(): IStrategicOpportunityData; + renderDefineStep(context: IDefineStepContext): React.ReactElement | null; + renderModals(context: IModalContext): React.ReactElement | null; + validateDefineStep(formValues: any): string | true; + marshalDefineStep(formValues: any, context: IMarshalContext): Partial; + onFormValuesChanged(changedValues: any, context: IFormChangeContext): void; + loadEditData(data: IStrategicOpportunityEditData, context: IEditLoadContext): void; + loadPrePopulationData?(params: URLSearchParams, context: IPrePopContext): Promise; + create(data: IStrategicOpportunityData): Promise; + update(opportunityId: number, data: IStrategicOpportunityData, shouldPopulate: boolean): Promise; + shouldRepopulateOnEdit(context: IRepopulateContext): boolean; + getMeasureTypeData(): IKeyNameItem[]; +} +``` + +**Key responsibilities:** +- `getDefaultData()` — return `defaultStrategicOpportunity{Type}Data` with correct `opportunityType` +- `renderDefineStep()` — render the type-specific Define component via `React.createElement` +- `validateDefineStep()` — return error string or `true` +- `marshalDefineStep()` — convert form values to data model fields +- `create()/update()` — call type-specific service methods +- `getMeasureTypeData()` — return the type-specific measure list + +**Props pattern:** +```typescript +export interface I{Type}StrategyProps { + form: FormInstance; + isActive: boolean; +} +``` + +**References:** +- `wizard/charge-code/useChargeCodeStrategy.ts` +- `wizard/general-ledger/useGLStrategy.ts` + +### F4. Define Component + +Create `strategic-opportunities/wizard/{type}/{Type}Define.tsx`: + +Type-specific Step 2 content — pickers, filters, measure type selection, desired result. + +**References:** +- `wizard/charge-code/ChargeCodeDefine.tsx` +- `wizard/general-ledger/GeneralLedgerDefine.tsx` + +### F5. Detail Store + +Create `strategic-opportunities/data/store/StrategicOpportunity{Type}DetailStoreTreeData.ts`: + +Extends `StoreTreeData` with the type-specific grid item interface. Handles: +- Loading data into tree structure +- Goal editing (onChange) +- Row config by dimensions + +**References:** +- `data/store/StrategicOpportunityDetailStoreTreeData.ts` (CC) +- `data/store/StrategicOpportunityGLDetailStoreTreeData.ts` (GL) + +### F6. Detail Grid + +Create `strategic-opportunities/detail/StrategicOpportunity{Type}DetailGrid.tsx`: + +DataGrid component with type-specific columns. Receives `root`, `columns`, `levelsCount`, `loading`, `expandedKeys`, `onChange`, `isEditable`. + +**References:** +- `detail/StrategicOpportunityDetailGrid.tsx` (CC) +- `detail/StrategicOpportunityGLDetailGrid.tsx` (GL) + +### F7. Layout Config + +In `strategic-opportunities/detail/data/IStrategicOpportunityLayout.ts`: + +1. Add default layout: +```typescript +export const defaultStrategicOpportunity{Type}Layout: IStrategicOpportunityLayout = { + rows: [strategicOpportunityDetailDimensionTotal, ...default{Type}Dimensions], + columns: [...default{Type}Columns] +}; +``` + +2. Add layout config: +```typescript +export const {type}OpportunityLayoutConfig: IDetailLayoutConfig = { + defaultLayout: defaultStrategicOpportunity{Type}Layout, + rowLayoutList: {type}RowLayoutList, + columnLayoutList: {type}ColumnLayoutList, + requiredRows: [StrategicOpportunityDetailDimension.{TypeDimension}], + requiredColumns: [{Type}DetailField.{RequiredColumn}] +}; +``` + +3. Update factory function: +```typescript +export function getDetailLayoutConfig(...): IDetailLayoutConfig { + if (opportunityType === OpportunityTypes.StrategicOpportunity{Type}) { + return {type}OpportunityLayoutConfig; + } + // ... existing cases +} +``` + +### F8. Dimension Enums + +In `strategic-opportunities/detail/data/StrategicOpportunityDetailDimension.ts`: + +1. Add dimension values to `StrategicOpportunityDetailDimension` enum (if new dimensions needed) +2. Add dimension ID to `StrategicOpportunityDetailDimensionId` enum +3. Create detail field enum: +```typescript +export enum StrategicOpportunity{Type}DetailField { + // Type-specific grid columns +} +``` +4. Create column name/header maps +5. Create field section enum + section-to-field mappings +6. Add helper functions for parsing columns/dimensions by string + +### F9. Service Methods + +In `strategic-opportunities/data/strategicOpportunityService.tsx`, add: +```typescript +createOpportunity{Type}: async (opportunity) => httpPost(`${url}/{type}`, opportunity), +updateOpportunity{Type}: async (id, opportunity, shouldPopulate) => + httpPut(`${url}/{type}/${id}`, { opportunity, shouldPopulateOpportunityDetails: shouldPopulate }), +getOpportunity{Type}: async (id) => httpGet(`${url}/${id}/GetOpportunity{Type}`), +getOpportunity{Type}DetailData: async (id, isTracking, dimensions) => + httpPost(`${url}/${id}/${isTracking ? '{type}-tracking-detail' : '{type}-detail'}`, dimensions), +updateOpportunity{Type}DetailsData: async (id, data) => + httpPut(`${url}/${id}/{type}-details`, data), +``` + +Also update `IStrategicOpportunityService.ts` interface with the new methods. + +### F10. Wizard Integration + +In `strategic-opportunities/wizard/StrategicOpportunity.tsx`: + +1. Import the new strategy hook: +```typescript +import { use{Type}Strategy } from './{type}/use{Type}Strategy'; +``` + +2. Add hook call (must always be called — React rules of hooks): +```typescript +const {type}Strategy = use{Type}Strategy({ form, isActive: opportunityType === OpportunityTypes.StrategicOpportunity{Type} }); +``` + +3. Update `activeStrategy` selection: +```typescript +const is{Type} = opportunityType === OpportunityTypes.StrategicOpportunity{Type}; +// All hooks always called +const ccStrategy = useChargeCodeStrategy({ form, isActive: !isGL && !is{Type} }); +const glStrategy = useGLStrategy({ form, isActive: isGL }); +const {type}Strategy = use{Type}Strategy({ form, isActive: is{Type} }); +const activeStrategy = is{Type} ? {type}Strategy : isGL ? glStrategy : ccStrategy; +``` + +### F11. Routes + +In `shared/Navigation.tsx`, add create and edit routes: +```tsx + StrategicOpportunity({ ...props, opportunityType: OpportunityTypes.StrategicOpportunity{Type} })} + key='/strategic-opportunities/create/{type}' +/> + StrategicOpportunity({ ...props, opportunityType: OpportunityTypes.StrategicOpportunity{Type} })} + key='/strategic-opportunities/edit/{type}' +/> +``` + +### F12. List Page + +In `strategic-opportunities/opportunities/StrategicItems.tsx`, add to the ButtonMenu items: +```typescript +items={[ + { key: 'charge-code', label: 'Charge Code' }, + { key: 'general-ledger', label: 'General Ledger' }, + { key: '{type}', label: '{Display Name}' } +]} +``` + +And handle the click: +```typescript +onClick={(e) => { + // ... existing cases + else if (e.key === '{type}') { + history.push('/strategic-opportunities/create/{type}'); + } +}} +``` + +--- + +## Cross-Cutting + +### X1. OpportunityTypes Enum + +Both backend and frontend must have matching values: + +**Backend** — `Biz/Generics/OpportunityTypes.cs` (or wherever `OpportunityTypes` is defined) +**Frontend** — `opportunities/opportunities/data/IAllOpportunity.ts` + +Current values: +``` +StrategicOpportunityChargeCode = 17 (approx) +StrategicOpportunityGL = 18 (approx) +``` +New type should be the next sequential value. + +### X2. Detail Page Integration + +In `strategic-opportunities/detail/StrategicOpportunityDetailPage.tsx`, add branches for: + +1. `storeTreeData` selection (around line 181): +```typescript +if (is{Type}) { + return new StrategicOpportunity{Type}DetailStoreTreeData(DefaultGridItem); +} +``` + +2. Detail data fetching (around line 252): +```typescript +if (is{Type}) { + // Fetch type-specific detail data +} +``` + +3. Grid column parsing (around line 305): +```typescript +if (is{Type}) { + _gridColumns = getStrategicOpportunity{Type}DetailColumnsByString(layout.columns); +} +``` + +4. Grid rendering (around line 853): +```typescript +is{Type} ? ( + +) : isGL ? ( + ... +) +``` + +5. Save handler — map dirty data to goal values for the new type. + +6. Edit navigation path: +```typescript +const typePath = is{Type} ? '{type}' : isGL ? 'general-ledger' : 'charge-code'; +``` + +7. Comments drawer `opportunityType` prop. + +### X3. Testing + +**Backend:** +- Unit tests for the detail service +- Unit tests for the distribution process +- Unit tests for new service methods + +**Frontend:** +- Unit test for the strategy hook (validate, marshal, create/update) +- Unit test for the Define component +- Unit test for the detail grid component + +--- + +## File Reference Table + +| Pattern | CC Reference | GL Reference | +|---------|-------------|--------------| +| Entity | `StrategicOpportunityChargeCode.cs` | `StrategicOpportunityGL.cs` | +| Create DTO | `StrategicOpportunityChargeCodeDto.cs` | `StrategicOpportunityGLDto.cs` | +| Update DTO | `StrategicOpportunityChargeCodeUpdateDto.cs` | `StrategicOpportunityGLUpdateDto.cs` | +| Detail Service | `StrategicChargeCodeOpportunityDetailService.cs` | `StrategicGLOpportunityDetailService.cs` | +| Distribution | `DistributionProcess/ChargeCode/` | `DistributionProcess/GL/` | +| Query Builder | `StrategicOpportunityDetailQueryBuilder.cs` | `StrategicOpportunityGLDetailQueryBuilder.cs` | +| Service Query | `StrategicOpportunityServiceQuery.cs` | `StrategicOpportunityGLServiceQuery.cs` | +| Data Model | `IStrategicOpportunityChargeCodeData.ts` | `IStrategicOpportunityGLData.ts` | +| Strategy Hook | `useChargeCodeStrategy.ts` | `useGLStrategy.ts` | +| Define Component | `ChargeCodeDefine.tsx` | `GeneralLedgerDefine.tsx` | +| Store | `StrategicOpportunityDetailStoreTreeData.ts` | `StrategicOpportunityGLDetailStoreTreeData.ts` | +| Detail Grid | `StrategicOpportunityDetailGrid.tsx` | `StrategicOpportunityGLDetailGrid.tsx` | + +## Tips and Gotchas + +1. **TPT PK collision**: Always check the generated migration for duplicate PK constraint names. Rename `pk_strategic_opportunity` to `pk_strategic_opportunity_{type}`. + +2. **React hooks order**: All strategy hooks must be called unconditionally in `StrategicOpportunity.tsx` (React rules of hooks). Use the `isActive` prop to no-op inactive strategies. + +3. **MeasureType sync**: Backend `MeasureType` enum values must exactly match frontend `MeasureType` const enum values. Double-check after adding. + +4. **Layout compatibility**: The `isLayoutCompatible()` function checks `requiredColumns`. Make sure your type's required columns are unique to avoid stale layouts from other types being loaded. + +5. **Formatting**: Run `npx eslint --fix ` after editing (not `npx prettier --write`). diff --git a/docs/strategic-opportunity-wizard-refactor.md b/docs/strategic-opportunity-wizard-refactor.md new file mode 100644 index 0000000..24ede22 --- /dev/null +++ b/docs/strategic-opportunity-wizard-refactor.md @@ -0,0 +1,404 @@ +# Strategic Opportunity Wizard Refactor — Migration Guide + +## Who This Is For + +If you wrote or maintain `StrategicOpportunity.tsx` (the wizard orchestrator) or any of its step components, this document explains what's changing, why, and how your code maps to the new structure. + +--- + +## Why Are We Doing This? + +The wizard was built for **Charge Code** opportunities only. Now we need to add **General Ledger (GL)** opportunities that use the same 5-step wizard flow but have completely different "Define Opportunity" logic (departments + GL accounts instead of encounter groups + charge codes). + +The current orchestrator (`StrategicOpportunity.tsx`, 670 lines) has charge-code-specific state, validation, data marshaling, and service calls baked directly in. If we added GL logic the same way, the file would balloon with `if (opportunityType === 'GL')` branches everywhere. + +Instead, we're extracting type-specific logic into **strategy hooks** — one for Charge Code, one for GL — and keeping the orchestrator focused on what's shared: wizard navigation, form management, and the steps that are identical for both types. + +--- + +## The Big Picture: Before vs After + +### Before (today) + +``` +StrategicOpportunity.tsx (670 lines) + ├── Wizard navigation + form setup + ├── 12+ CC-specific state variables (chargeCodeMembers, selectedChargeCodes, etc.) + ├── CC-specific functions (loadChargeCodePickerModels, etc.) + ├── CC-specific validation in handleChange (step 1) + ├── CC-specific data marshaling in updateOpportunity (step 1) + ├── CC-specific edit loading in loadEditData + ├── CC-specific pre-population from exploration + ├── CC-specific create/update service calls + └── CC-specific repopulation detection on edit +``` + +Everything lives in one file. There's no separation between "wizard infrastructure" and "charge code logic." + +### After (refactored) + +``` +StrategicOpportunity.tsx (~300 lines) — Generic orchestrator + ├── Wizard navigation + form setup (unchanged) + ├── Shared state only (primaryMeasures, filters, rollups, workbooks, baseline) + ├── Calls activeStrategy.validateDefineStep() instead of inline CC validation + ├── Calls activeStrategy.marshalDefineStep() instead of inline CC marshaling + ├── Calls activeStrategy.loadEditData() for type-specific edit fields + ├── Calls activeStrategy.create() / activeStrategy.update() + └── Renders activeStrategy.renderDefineStep() for step 1 + +charge-code/useChargeCodeStrategy.ts (~250 lines) — All the CC logic extracted + ├── CC state (chargeCodeMembers, selectedChargeCodes, etc.) + ├── CC functions (loadChargeCodePickerModels, etc.) + ├── CC step 1 validation + marshaling + ├── CC edit loading + pre-population + └── CC create/update service calls + +general-ledger/useGLStrategy.ts — New GL logic + ├── GL state (accountType, departments, GL accounts) + ├── GL step 1 validation + marshaling + ├── GL edit loading + └── GL create/update service calls +``` + +--- + +## What Stays the Same + +These things are **not changing** (or changing only trivially): + +| Component | What's happening | +|-----------|-----------------| +| **StrategicOpportunityDetail.tsx** (Step 0) | Shared. No change except minor display support for GL type. | +| **StrategicOpportunityDefineTimeframe.tsx** (Step 3) | Shared. No change. | +| **StrategicOpportunityExternalLinks.tsx** (Step 4) | Shared. No change. | +| **StrategicOpportunityRenameMeasure.tsx** | Shared utility. No change. | +| **StrategicOpportunityFilters.tsx** | Shared utility. No change. | +| **All data/ files** | No changes to interfaces, services, enums. | +| **Wizard step validation for steps 0, 2, 3, 4** | Stays in the orchestrator — these steps are identical for both types. | + +--- + +## What's Moving + +### Files that move (same code, new location) + +| Old Location | New Location | Why | +|-------------|-------------|-----| +| `wizard/StrategicOpportunityDefine.tsx` | `wizard/charge-code/ChargeCodeDefine.tsx` | This component is 100% charge-code-specific. Renaming makes that explicit. | +| `wizard/ChargeCodeCustomPickerModal.tsx` | `wizard/charge-code/ChargeCodeCustomPickerModal.tsx` | Only used by charge code define step. | + +These are **moves + renames**, not rewrites. The component logic inside is the same. + +### State that moves from orchestrator → CC strategy hook + +All of these `useState` calls currently in `StrategicOpportunity.tsx` will move into `useChargeCodeStrategy.ts`: + +```typescript +// These move OUT of the orchestrator: +const [chargeCodeMembers, setChargeCodeMembers] = useState(...); +const [selectedChargeCodes, setSelectedChargeCodes] = useState(...); +const [selectedChargeCodesInitial, setSelectedChargeCodesInitial] = useState(...); +const [expPopulationMembers, setExpPopulationMembers] = useState(...); +const [selectedExpPopulations, setSelectedExpPopulations] = useState(...); +const [patientPopulationsInitial, setPatientPopulationsInitial] = useState(...); +const [expPopulationsInitial, setExpPopulationsInitial] = useState(...); +const [expPopCostDriver, setExpPopCostDriver] = useState(...); +const [uploadModalVisible, setUploadModalVisible] = useState(...); +const [isPrePopulated, setIsPrePopulated] = useState(...); +``` + +```typescript +// These STAY in the orchestrator (shared by both types): +const [stepNumber, setStepNumber] = useState(0); +const [opportunity, setOpportunity] = useState(...); // now typed as IStrategicOpportunityData (base) +const [primaryMeasures, setPrimaryMeasures] = useState(...); +const [workbooks, setWorkbooks] = useState(...); +const [opportunityFilters, setOpportunityFilters] = useState(...); +const [baselinePeriodInitial, setBaselinePeriodInitial] = useState(...); +const [rollupColumns, setRollupColumns] = useState(...); +const [isEditMode, setIsEditMode] = useState(false); +const [loading, setLoading] = useState(false); +``` + +### Functions that move from orchestrator → CC strategy hook + +| Function | Why it's CC-specific | +|----------|---------------------| +| `loadChargeCodePickerModels()` | Fetches charge code picker data | +| `loadExpPopulationPickerModels()` | Fetches exploration populations | +| `setPrimaryMeasureChargeCodes()` | Sets CC-specific form field | +| `setExplorationPopulations()` | Sets CC-specific encounter group | +| `handleChargeCodesUploaded()` | Handles CC upload modal result | +| Pre-population `useEffect` (lines 584-642) | Only applies to CC from exploration | +| CC-specific parts of `loadEditData` | Loading encounter groups, charge codes | +| CC-specific parts of `handleFormValuesChanged` | CC form field change handling | +| Step 1 block in `handleChange` | CC-specific validation checks | +| Step 1 block in `updateOpportunity` | CC-specific data marshaling | +| `handleSubmit` service calls | `createOpportunityChargeCode` / `updateOpportunityChargeCode` | +| Repopulation detection logic | Comparing CC-specific initial vs current state | + +--- + +## The Strategy Interface + +Both hooks implement this common interface so the orchestrator doesn't need to know which type it's working with: + +```typescript +interface IOpportunityTypeStrategy { + // Default data for creating a new opportunity of this type + getDefaultData(): IStrategicOpportunityData; + + // Renders the "Define Opportunity" step (step 1) — CC shows encounter groups + charge codes, GL shows departments + accounts + renderDefineStep(context): JSX.Element; + + // Renders any modals this type needs (CC has upload modal, GL has none) + renderModals(context): JSX.Element | null; + + // Returns true if step 1 form values are valid for this type + validateDefineStep(formValues): boolean; + + // Converts form values into opportunity data fields for step 1 + marshalDefineStep(formValues, context): Partial; + + // Handles form value changes that need type-specific reactions + onFormValuesChanged(changedValues, context): void; + + // Loads type-specific fields when editing an existing opportunity + loadEditData(data, form, context): void; + + // (CC only) Pre-populates from exploration population URL params + loadPrePopulationData?(params, form, context): Promise; + + // Calls the correct create API + create(data): Promise; + + // Calls the correct update API + update(opportunityId, data, shouldPopulate): Promise; + + // Determines if editing has changed enough to require goal recalculation + shouldRepopulateOnEdit(context): boolean; + + // Returns the measure type list for the "Additional Measures" step dropdown + getMeasureTypeData(): { key: number; name: string }[]; +} +``` + +--- + +## How the Orchestrator Changes + +### Before: inline CC logic + +```typescript +// In handleChange, step 1 validation: +} else if (currentStep === 1) { + if (!detailValues.encounterGroup || !detailValues.encounterGroups || ...) { + return; + } +} +``` + +### After: delegates to strategy + +```typescript +} else if (currentStep === 1) { + if (!activeStrategy.validateDefineStep(detailValues)) { + return; + } +} +``` + +### Before: inline CC rendering + +```typescript + + + +``` + +### After: strategy renders its own step + +```typescript + + {activeStrategy.renderDefineStep({ form, filters, updateFilters, ... })} + +``` + +### Before: inline CC service call + +```typescript +const opportunityId = await strategicOpportunityService.createOpportunityChargeCode(data); +``` + +### After: strategy handles service call + +```typescript +const opportunityId = await activeStrategy.create(data); +``` + +--- + +## Hook Selection: Both Hooks Always Called + +React requires hooks to be called unconditionally (same hooks, same order, every render). So the orchestrator calls **both** hooks every time: + +```typescript +const ccStrategy = useChargeCodeStrategy({ form, ... }); +const glStrategy = useGLStrategy({ form, ... }); + +const activeStrategy = opportunityType === OpportunityTypes.StrategicOpportunityGL + ? glStrategy + : ccStrategy; +``` + +The inactive hook holds trivial default state — a few empty arrays and `false` booleans. No API calls, no heavy computation. This is a standard React pattern. + +--- + +## New Folder Structure + +``` +strategic-opportunities/ + wizard/ + StrategicOpportunity.tsx ← Slimmed-down orchestrator + StrategicOpportunityDetail.tsx ← Step 0 (unchanged) + StrategicOpportunityDefineAdditional.tsx ← Step 2 (adds measureTypeData prop) + StrategicOpportunityDefineTimeframe.tsx ← Step 3 (unchanged) + StrategicOpportunityExternalLinks.tsx ← Step 4 (unchanged) + StrategicOpportunityRenameMeasure.tsx ← Shared utility (unchanged) + StrategicOpportunityFilters.tsx ← Shared utility (unchanged) + types/ + IOpportunityTypeStrategy.ts ← Strategy interface + context types + charge-code/ + useChargeCodeStrategy.ts ← CC strategy hook (extracted from orchestrator) + ChargeCodeDefine.tsx ← Moved from StrategicOpportunityDefine.tsx + ChargeCodeCustomPickerModal.tsx ← Moved from wizard root + general-ledger/ + useGLStrategy.ts ← GL strategy hook (new) + GeneralLedgerDefine.tsx ← GL define step (new) +``` + +--- + +## StrategicOpportunityDefineAdditional Change + +This component currently hardcodes the charge code measure type list: + +```typescript +// Line 18 — imports CC-only measure types +import { getDesiredResultByMeasureType, measureTypeData } from '../data/MeasureType'; + +// Line 31 — hardcoded to CC measures +const secondaryMeasureItems = useMemo(() => measureTypeData.map(...), []); +``` + +After the refactor, it receives `measureTypeData` as a **prop** from the orchestrator, which gets it from `activeStrategy.getMeasureTypeData()`. This way CC shows CC measures and GL shows GL measures in the "Add Additional Measure" dropdown. + +```typescript +// Before + + +// After — adds measureTypeData prop + +``` + +--- + +## New Routes + +Two new routes added to `Navigation.tsx`: + +| Route | Purpose | +|-------|---------| +| `/strategic-opportunities/create/general-ledger` | Create a new GL strategic opportunity | +| `/strategic-opportunities/edit/general-ledger/:strategicOpportunityId` | Edit an existing GL strategic opportunity | + +These follow the exact same pattern as the existing CC routes (lines 226-236 in Navigation.tsx). + +--- + +## What the GL Define Step Looks Like + +Instead of Encounter Groups + Charge Codes + Upload, the GL define step has: + +1. **Account Type** — Radio: Revenue / Expense / Margin +2. **Department** — ScoreMemberPicker for GL departments +3. **GL Accounts** — Conditional pickers based on account type: + - Revenue → Revenue account picker + - Expense → Expense account picker + - Margin → Both revenue + expense pickers + - Optional statistic account picker (checkbox toggle) +4. **Filters** — Same shared StrategicFilterBar component +5. **Primary Measure** — Dropdown from `glMeasureTypeData` (instead of `measureTypeData`) +6. **Desired Result + Threshold** — Same UI as CC + +--- + +## How to Verify Nothing Broke + +After the refactor, test these CC flows — they should behave **identically** to today: + +1. **Create CC opportunity**: Navigate to `/strategic-opportunities/create/charge-code`, complete all 5 steps, verify it saves +2. **Edit CC opportunity**: Open an existing CC strategic opportunity, click Edit, verify all fields load correctly, make a change, save +3. **Pre-populate from exploration**: From an exploration population, click "Create Strategic Opportunity" — verify encounter group, charge codes, and baseline are pre-filled +4. **Goal reset warning**: Edit an opportunity, change the filters or baseline period, click Save — verify the "resets goals" confirmation modal appears +5. **Run frontend tests**: `cd src/Strata.ContinuousImprovement.Web/continuousimprovement && npm run test-ci` + +--- + +## Mapping Cheat Sheet: "Where Did My Code Go?" + +| If you're looking for... | It's now in... | +|--------------------------|---------------| +| `chargeCodeMembers` state | `useChargeCodeStrategy.ts` | +| `selectedChargeCodes` state | `useChargeCodeStrategy.ts` | +| `loadChargeCodePickerModels()` | `useChargeCodeStrategy.ts` | +| `loadExpPopulationPickerModels()` | `useChargeCodeStrategy.ts` | +| `handleChargeCodesUploaded()` | `useChargeCodeStrategy.ts` | +| Pre-population `useEffect` | `useChargeCodeStrategy.ts` → `loadPrePopulationData()` | +| `StrategicOpportunityDefine` component | `charge-code/ChargeCodeDefine.tsx` (same code, new name) | +| `ChargeCodeCustomPickerModal` | `charge-code/ChargeCodeCustomPickerModal.tsx` (moved) | +| Step 1 validation in `handleChange` | `useChargeCodeStrategy.ts` → `validateDefineStep()` | +| Step 1 marshaling in `updateOpportunity` | `useChargeCodeStrategy.ts` → `marshalDefineStep()` | +| `createOpportunityChargeCode` call | `useChargeCodeStrategy.ts` → `create()` | +| `updateOpportunityChargeCode` call | `useChargeCodeStrategy.ts` → `update()` | +| Repopulation check (`shouldPopulateOpportunityDetails`) | `useChargeCodeStrategy.ts` → `shouldRepopulateOnEdit()` | +| Wizard navigation (`handleChange`, `setStepNumber`) | `StrategicOpportunity.tsx` (stayed) | +| Form setup (`Form.useForm()`) | `StrategicOpportunity.tsx` (stayed) | +| Shared state (primaryMeasures, filters, rollups) | `StrategicOpportunity.tsx` (stayed) | +| Steps 0, 2, 3, 4 validation | `StrategicOpportunity.tsx` (stayed) | +| `handleCancel`, `handleSubmit` skeleton | `StrategicOpportunity.tsx` (stayed) | + +--- + +## FAQ + +**Q: Why not just use `if/else` for GL vs CC in the existing orchestrator?** +A: The orchestrator would grow to 1000+ lines with interleaved branching. Strategy hooks keep each type's logic cohesive and independently testable. + +**Q: Why hooks instead of classes?** +A: React state (`useState`, `useEffect`) only works inside hooks or components. A class-based strategy would need workarounds to manage React state. + +**Q: Does the inactive strategy hook waste memory?** +A: Negligible. It holds a few empty arrays and `false` booleans. No API calls fire for the inactive type. + +**Q: Will this break existing CC tests?** +A: No. The CC behavior is identical — logic is just relocated. Import paths in tests may need updating if they reference moved files. + +**Q: Can I still debug the CC flow the same way?** +A: Yes. Set breakpoints in `useChargeCodeStrategy.ts` for CC-specific logic, or in `StrategicOpportunity.tsx` for wizard navigation. The call chain is just one level deeper for type-specific operations. diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..bde477f --- /dev/null +++ b/nuget.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..81bfdf9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1717 @@ +{ + "name": "continuousimprovement", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@commitlint/cli": "^19.2.1", + "@commitlint/config-conventional": "^19.1.0", + "@commitlint/prompt-cli": "^19.2.1", + "husky": "^9.0.11" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@commitlint/cli": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz", + "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==", + "dev": true, + "dependencies": { + "@commitlint/format": "^19.8.1", + "@commitlint/lint": "^19.8.1", + "@commitlint/load": "^19.8.1", + "@commitlint/read": "^19.8.1", + "@commitlint/types": "^19.8.1", + "tinyexec": "^1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", + "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", + "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.1", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/ensure": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz", + "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.1", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz", + "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==", + "dev": true, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz", + "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", + "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/lint": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz", + "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==", + "dev": true, + "dependencies": { + "@commitlint/is-ignored": "^19.8.1", + "@commitlint/parse": "^19.8.1", + "@commitlint/rules": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz", + "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==", + "dev": true, + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/execute-rule": "^19.8.1", + "@commitlint/resolve-extends": "^19.8.1", + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^6.1.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/message": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", + "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", + "dev": true, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", + "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/prompt": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/prompt/-/prompt-19.8.1.tgz", + "integrity": "sha512-IH3zJBJ2GSffgAJtW5EklK72skEAa3PZsbWQ5+Ybumd9QjKMnI9vagz5lprGRImdysaFREsMCOVpmcWjxpxPxg==", + "dev": true, + "dependencies": { + "@commitlint/ensure": "^19.8.1", + "@commitlint/load": "^19.8.1", + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0", + "inquirer": "^9.2.15" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/prompt-cli": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/prompt-cli/-/prompt-cli-19.8.1.tgz", + "integrity": "sha512-rK92LywMpGKpBg730sRVRujUF3yP+xSLdfYpYSPK6jCbhVvTRPcBYVi3plIBR1VETto97+hN1HHTTpZsN8jSUA==", + "dev": true, + "dependencies": { + "@commitlint/prompt": "^19.8.1", + "inquirer": "^9.2.15", + "tinyexec": "^1.0.0" + }, + "bin": { + "commit": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", + "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==", + "dev": true, + "dependencies": { + "@commitlint/top-level": "^19.8.1", + "@commitlint/types": "^19.8.1", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz", + "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==", + "dev": true, + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/types": "^19.8.1", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/rules": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", + "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==", + "dev": true, + "dependencies": { + "@commitlint/ensure": "^19.8.1", + "@commitlint/message": "^19.8.1", + "@commitlint/to-lines": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz", + "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==", + "dev": true, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz", + "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==", + "dev": true, + "dependencies": { + "find-up": "^7.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", + "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", + "dev": true, + "dependencies": { + "undici-types": "~7.10.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", + "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.1.0.tgz", + "integrity": "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==", + "dev": true, + "dependencies": { + "jiti": "^2.4.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", + "dev": true, + "dependencies": { + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inquirer": { + "version": "9.3.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.7.tgz", + "integrity": "sha512-LJKFHCSeIRq9hanN14IlOtPSTe3lNES7TYDTE2xxdAy1LS5rYphajK1qtwvj3YmQXvvk0U2Vbmcni8P9EIQW9w==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b00d202 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "scripts": { + "commit": "commit", + "prepare": "husky", + "generate-swagger": "node scripts/fetch-swagger.js" + }, + "devDependencies": { + "@commitlint/cli": "^19.2.1", + "@commitlint/prompt-cli": "^19.2.1", + "@commitlint/config-conventional": "^19.1.0", + "husky": "^9.0.11" + } +} \ No newline at end of file diff --git a/scripts/fetch-swagger.js b/scripts/fetch-swagger.js new file mode 100644 index 0000000..bcc22f4 --- /dev/null +++ b/scripts/fetch-swagger.js @@ -0,0 +1,37 @@ +#!/usr/bin/env node +/** + * Generates swagger.json from the API DLL using the Swashbuckle CLI. + * Does NOT require the API to be running. + * + * Prerequisites: + * dotnet tool install --global Swashbuckle.AspNetCore.Cli --version 6.8.1 + * dotnet build src/Strata.ContinuousImprovement.Api/ + * + * Usage: npm run generate-swagger + */ + +const { execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); + +const ROOT = path.join(__dirname, '..'); +const DLL = path.join(ROOT, 'src/Strata.ContinuousImprovement.Api/bin/Debug/net8.0/Strata.ContinuousImprovement.Api.dll'); +const OUTPUT = path.join(ROOT, 'src/Strata.ContinuousImprovement.Web/continuousimprovement/swagger.json'); +const DOC_NAME = 'v1'; + +if (!fs.existsSync(DLL)) { + console.log('API DLL not found — building first...'); + execSync('dotnet build src/Strata.ContinuousImprovement.Api/ --configuration Debug', { + cwd: ROOT, + stdio: 'inherit', + }); +} + +console.log('Generating swagger.json from API DLL...'); +execSync(`swagger tofile --output "${OUTPUT}" "${DLL}" ${DOC_NAME}`, { + cwd: ROOT, + stdio: 'inherit', +}); + +const size = fs.statSync(OUTPUT).size; +console.log(`swagger.json written to ${OUTPUT} (${(size / 1024).toFixed(1)} KB)`); diff --git a/src/Get_Octo_Information.ps1 b/src/Get_Octo_Information.ps1 new file mode 100644 index 0000000..31ceaca --- /dev/null +++ b/src/Get_Octo_Information.ps1 @@ -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.continuousimprovement"). +.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.continuousimprovement", + [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 diff --git a/src/Get_Tags_and_messages.ps1 b/src/Get_Tags_and_messages.ps1 new file mode 100644 index 0000000..9fbc634 --- /dev/null +++ b/src/Get_Tags_and_messages.ps1 @@ -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/continuousimprovement/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 = "- CI -", + [string]$pr_path = (Join-Path $PSScriptRoot ".."), + [string]$pr_giturl = "https://github.com/stratadecision/continuousimprovement/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\continuousimprovement" + idx = $pr_patch # 7 + range = $pr_minor_start..$pr_minor_end # 587..630 + codeFreeze = $pr_codeFreeze # "0.614.0" + qaRelease = $pr_qaRelease # "0.602.1" + 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 \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/ActionResults/ExcelContentResult.cs b/src/Strata.ContinuousImprovement.Api/ActionResults/ExcelContentResult.cs new file mode 100644 index 0000000..5a964d2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/ActionResults/ExcelContentResult.cs @@ -0,0 +1,49 @@ +using ClosedXML.Excel; +using Microsoft.AspNetCore.Mvc; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api +{ + public class ExcelContentResult : ActionResult + { + public IXLWorkbook Workbook { get; set; } + public string FileName { get; set; } + + public ExcelContentResult() + { + } + + public ExcelContentResult(IXLWorkbook workbook, string fileName = "Export") : base() + { + Workbook = workbook; + FileName = fileName; + } + + public override void ExecuteResult(ActionContext context) + { + GetFileContentResult().ExecuteResult(context); + } + + public override Task ExecuteResultAsync(ActionContext context) + { + return GetFileContentResult().ExecuteResultAsync(context); + } + + protected FileContentResult GetFileContentResult() + { + // Flush the workbook to the Response.OutputStream + using (var memoryStream = new MemoryStream()) + { + Workbook.SaveAs(memoryStream); + + return new FileContentResult(memoryStream.ToArray(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + { + FileDownloadName = $"{FileName}_{DateTime.UtcNow:MM-dd-yyyy}.xlsx" + }; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Attributes/DisableFormValueModelBindingAttribute.cs b/src/Strata.ContinuousImprovement.Api/Attributes/DisableFormValueModelBindingAttribute.cs new file mode 100644 index 0000000..c619ae7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Attributes/DisableFormValueModelBindingAttribute.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using System; + +namespace Strata.ContinuousImprovement.Api.Attributes +{ + /// + /// 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. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter + { + public void OnResourceExecuting(ResourceExecutingContext context) + { + var factories = context.ValueProviderFactories; + factories.RemoveType(); + factories.RemoveType(); + factories.RemoveType(); + } + + public void OnResourceExecuted(ResourceExecutedContext context) + { + + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/AllOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/AllOpportunitiesController.cs new file mode 100644 index 0000000..84a7798 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/AllOpportunitiesController.cs @@ -0,0 +1,107 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.All.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class AllOpportunitiesController : OpportunityController + { + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + private readonly ILogger _logger; + private readonly ClaimsPrincipal _user; + + public AllOpportunitiesController(IAllOpportunityService opportunityService, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IFeatureFlagServiceClient featureFlagServiceClient, + ILogger logger) + : base(opportunityService) + { + _featureFlagServiceClient = featureFlagServiceClient; + _logger = logger; + _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + } + + [HttpPost("")] + public async Task> GetOpportunities(AllOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + var hasFlexibleExplorartion = _user.IsSdtEmployee() + || (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationenabled", databaseGuid, cancellationToken: cancellationToken)); + var dict = new Dictionary { { "isSdtEmployee", _user.IsSdtEmployee() }, { "hasFlexibleExploration", hasFlexibleExplorartion } }; + _logger.LogServiceInformation("CI Flexible Exploration Enabled", dict); + loadOptions.Filters.HasFlexibleExploration = hasFlexibleExplorartion; + return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpPost("[action]")] + public async Task IsNameValid(ValidateName name, CancellationToken cancellationToken) + { + return await ((IAllOpportunityService)OpportunityService).NameIsValid(name.ConfigurationGuid, name.OpportunityType, name.Name, name?.OpportunityGuid, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Export(AllOpportunityExportOptions exportOptions, CancellationToken cancellationToken) + { + var workbook = await ((IAllOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken); + return new ExcelContentResult(workbook, "EN_Opportunities"); + + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + var isStrataUser = _user.IsSdtEmployee(); + var filterOptions = await ((IAllOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken); + if (!isStrataUser) + { + filterOptions.OpportunityTypes = filterOptions.OpportunityTypes.Where(type => Int32.Parse(type.Id) != (int)OpportunityTypes.Blank); + } + return filterOptions; + } + } + + [ExcludeFromCodeCoverage] + public class ValidateName + { + public Guid ConfigurationGuid { get; set; } + public int OpportunityType { get; set; } + public string Name { get; set; } + public Guid? OpportunityGuid { get; set; } + } + + [ExcludeFromCodeCoverage] + public class AllOpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public AllFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class AllOpportunityExportOptions + { + public SortOptions SortOptions { get; set; } + public AllFilters Filters { get; set; } + } + +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/BackgroundClientJobController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/BackgroundClientJobController.cs new file mode 100644 index 0000000..6ed8bea --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/BackgroundClientJobController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.BackgroundClientJob; +using System.Threading; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class BackgroundClientJobController(IBackgroundClientJobService backgroundClientJobService) : ControllerBase + { + [HttpGet("{jobId}/status")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public ActionResult GetJobStatus(string jobId, CancellationToken cancellationToken) + { + var status = backgroundClientJobService.GetJobStatus(jobId, cancellationToken); + if (status == "NotFound") + { + return NotFound(status); + } + return Ok(status); + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeInitiativesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeInitiativesController.cs new file mode 100644 index 0000000..a40e9e6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeInitiativesController.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ChargeCodeInitiativesController : ControllerBase + { + private readonly IClaimsPrincipalAccessor _claimsPrincipalAccessor; + private readonly ICCInitiativeService _chargeCodeInitiativeService; + private readonly IStakeholderUser _stakeholderUser; + + public ChargeCodeInitiativesController( + IClaimsPrincipalAccessor claimsPrincipalAccessor, + ICCInitiativeService chargeCodeInitiativeService, + IStakeholderUser stakeholderUser) + { + _claimsPrincipalAccessor = claimsPrincipalAccessor; + _chargeCodeInitiativeService = chargeCodeInitiativeService; + _stakeholderUser = stakeholderUser; + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> Create(InitiativeDto initiative, CancellationToken cancellationToken) + { + try + { + var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + var newInitiative = initiative.MapToInitiative(_stakeholderUser); + var dbGuid = user.GetDatabaseGuid(); + return await _chargeCodeInitiativeService.CreateChargeCodeInitiative(dbGuid, newInitiative, initiative.OpportunityGuid, cancellationToken); + } + catch + { + return Guid.Empty; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeOpportunitiesController.cs new file mode 100644 index 0000000..73b316a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeOpportunitiesController.cs @@ -0,0 +1,264 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.OrgDefined; +using Strata.ContinuousImprovement.Biz.OrgDefined.Queries; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ChargeCodeOpportunitiesController : OpportunityController + { + private readonly IClaimsPrincipalAccessor _claimsPrincipalAccessor; + private readonly IConfigurationService _configurationService; + private readonly ICCOpportunityService _opportunityService; + private readonly ICCInitiativeService _initiativeService; + private readonly IOpportunityMetricCohortService _opportunityMetricCohortService; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + private readonly IAllOpportunityService _allOpportunityService; + + public ChargeCodeOpportunitiesController( + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IConfigurationService configurationService, + ICCOpportunityService opportunityService, + ICCInitiativeService initiativeService, + IOpportunityMetricCohortService opportunityMetricCohortService, + IFeatureFlagServiceClient featureFlagServiceClient, + IAllOpportunityService allOpportunityService) : base(opportunityService) + { + _configurationService = configurationService; + _claimsPrincipalAccessor = claimsPrincipalAccessor; + _opportunityService = opportunityService; + _initiativeService = initiativeService; + _opportunityMetricCohortService = opportunityMetricCohortService; + _featureFlagServiceClient = featureFlagServiceClient; + _allOpportunityService = allOpportunityService; + } + + [HttpPut("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> UpdateOpportunity(Guid opportunityGuid, [FromBody] CCOpportunitySaveModel opportunity, CancellationToken cancellationToken) + { + var isNameValid = await _allOpportunityService.NameIsValid(opportunity.ConfigurationGuid, (int)OpportunityTypes.ChargeCode, opportunity.Name, + opportunity?.OpportunityGuid, cancellationToken); + if (!isNameValid) + { + return BadRequest(); + } + var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + var updatedOpportunity = opportunity.MapToDataModel(); + var dbGuid = user?.GetDatabaseGuid() ?? Guid.Empty; + var result = await _opportunityService.UpdateAsync(dbGuid, updatedOpportunity, cancellationToken); + return Ok(result); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> ValidateChargeCodesAsync([FromBody] ValidateChargeCodeDto dto, CancellationToken cancellationToken) + { + var result = await _opportunityService.ValidateChargeCodesAsync(dto.ChargeCodes, cancellationToken); + return Ok(result); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetChargeCodeMembers([FromBody] List hPaths, CancellationToken cancellationToken) + { + var result = await _opportunityService.GetChargeCodePickerModelMembers(hPaths, cancellationToken); + return Ok(result); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> ValidateOpportunityLevelsAsync([FromBody] OpportunityLevelSelectionInfo info, CancellationToken cancellationToken) + { + var result = await _opportunityService.ValidateOpportunityLevelsAsync(info.OpportunityLevel, info.OpportunityLevels, info.Configuration, cancellationToken); + return Ok(result); + } + + [HttpDelete("{id}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task Delete(Guid id, CancellationToken cancellationToken) + => await OpportunityService.DeleteAsync(id, cancellationToken); + + [HttpGet("{opportunityGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetById(Guid opportunityGuid, CancellationToken cancellationToken) + { + var opportunity = await _opportunityService.GetById(opportunityGuid, cancellationToken); + if (opportunity == null) + { + return NotFound(); + } + var opportunityDto = opportunity.MapToDto(); + return Ok(opportunityDto); + } + + [HttpGet("{opportunityGuid}/[action]")] + public async Task GetEditModel(Guid opportunityGuid, CancellationToken cancellationToken) + { + var opportunity = await _opportunityService.GetById(opportunityGuid, cancellationToken); + return opportunity.MapToSaveModel(); + } + + [HttpPut("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task UpdateGoalTotalUnitsOfService(Guid opportunityGuid, [FromBody] GoalSetDto goalSetDto, CancellationToken cancellationToken) + { + var hasUpdatedUOS = await _opportunityService.UpdateGoalTotalUnitsOfServiceAsync(opportunityGuid, goalSetDto.Key, goalSetDto.GoalValue, cancellationToken); + if (hasUpdatedUOS) + { + return await _initiativeService.UpdateSavings(opportunityGuid, cancellationToken); + } + + return false; + } + + [HttpPut("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task UpdateGoalCostPerUnit(Guid opportunityGuid, [FromBody] GoalSetDto goalSetDto, CancellationToken cancellationToken) + { + var hasUpdatedCostPerUnit = await _opportunityService.UpdateGoalCostPerUnitAsync(opportunityGuid, goalSetDto.Key, goalSetDto.GoalValue, cancellationToken); + if (hasUpdatedCostPerUnit) + { + return await _initiativeService.UpdateSavings(opportunityGuid, cancellationToken); + } + + return false; + } + + [HttpPut("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> UpdateGoals(Guid opportunityGuid, [FromBody] IEnumerable changes, CancellationToken cancellationToken) + { + var existsOpportunity = await _opportunityService.Contains(opportunityGuid, cancellationToken); + if (!existsOpportunity) + { + return NotFound(); + } + + var hasUpdatedCostPerUnit = await _opportunityService.UpdateGoalsAsync(opportunityGuid, changes, cancellationToken); + + if (hasUpdatedCostPerUnit) + { + return await _initiativeService.UpdateSavings(opportunityGuid, cancellationToken); + } + + return false; + } + + [HttpPost("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status202Accepted)] + public async Task Export( + [FromRoute] Guid opportunityGuid, + [FromBody] ChargeCodeItems chargeCodes, CancellationToken cancellationToken) + { + var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + var workbook = await _opportunityService.ExportAsync(opportunityGuid, chargeCodes.ChargeCodes, user.Identity?.Name, cancellationToken); + return new ExcelContentResult(workbook, "ChargeCodeTemplate"); + } + + [HttpGet("[action]")] + public async Task OpportunityCreateEnabled(CancellationToken cancellationToken) + { + var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + return await _featureFlagServiceClient.IsEnabledAsync("chargecodebeta", user.GetDatabaseGuid(), cancellationToken: cancellationToken); + } + + public class GoalSetDto + { + public string Key { get; set; } + public double GoalValue { get; set; } + } + + [HttpGet("{opportunityGuid}/[action]")] + public async Task GetInitiativeInfo(Guid opportunityGuid, CancellationToken cancellationToken) + { + return await _opportunityService.GetInitiativeInfoAsync(opportunityGuid, cancellationToken); + } + + [HttpPost("[action]")] + public async Task Refresh(RefreshData refreshData, CancellationToken cancellationToken) + { + return await _opportunityService.RefreshOpportunityTrackingData(refreshData.DatabaseGuid, refreshData.Guid, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> OpportunityMetricCohortData([FromBody] OpportunityMetricCohortOptions options, CancellationToken cancellationToken) + { + var config = await _configurationService.GetConfigurationAsync(options.ConfigurationGuid, cancellationToken); + if (config == null) + { + return BadRequest(); + } + var result = Enumerable.Empty(); + result = await _opportunityMetricCohortService.GetChargeCodeOpportunityMetricCohortData(config, options.Data, options.InnerJoin, cancellationToken); + return Ok(result); + } + + public class RefreshData + { + public Guid DatabaseGuid { get; set; } + public Guid Guid { get; set; } + } + + public class ChargeCodeItems + { + public IEnumerable ChargeCodes { get; set; } + + } + + public class OpportunityLevelSelectionInfo + { + public OpportunityLevel OpportunityLevel { get; set; } + public List OpportunityLevels { get; set; } + public Guid Configuration { get; set; } + } + + public class ValidateChargeCodeDto + { + public List ChargeCodes { get; set; } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeOpportunityDetailsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeOpportunityDetailsController.cs new file mode 100644 index 0000000..2dcfaa1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeOpportunityDetailsController.cs @@ -0,0 +1,98 @@ +using Hangfire; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.ChargeCode.Jobs; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ChargeCodeOpportunityDetailsController : ControllerBase + { + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly ICCOpportunityClientJob _ccOpportunityClientJob; + private readonly ILogger _logger; + + public ChargeCodeOpportunityDetailsController(IBackgroundJobClient backgroundJobClient, ICCOpportunityClientJob ccOpportunityClientJob, ILogger logger) + { + _backgroundJobClient = backgroundJobClient; + _ccOpportunityClientJob = ccOpportunityClientJob; + _logger = logger; + } + + [Obsolete("Replacing w/ explicitly named endpoints")] + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public void Create([FromBody] ChargeCodeDetailInfo detailInfo, CancellationToken cancellationToken) + { + _backgroundJobClient.Enqueue(job + => job.PopulateOpportunityDetailsAsync(detailInfo.StrataId, string.Empty, detailInfo.OpportunityGuid, detailInfo.IsBaseline, detailInfo.IsUpdate, false, cancellationToken)); + } + + + [HttpPost("refresh-baseline-details")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task RefreshBaselineDetails( + [FromBody] ChargeCodeOpportunityJobRequestInfo request, + CancellationToken cancellationToken) + { + try + { + var jobId = await _ccOpportunityClientJob.RefreshClientOpportunityBaselineDetails( + request.StrataId, request.OpportunityGuid, cancellationToken); + return Ok(jobId); + } + catch (InvalidOperationException ex) + { + _logger.LogServiceError(ex); + return BadRequest(); + } + } + + [HttpPost("refresh-tracking-details")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task RefreshTrackingDetails( + [FromBody] ChargeCodeOpportunityJobRequestInfo request, + CancellationToken cancellationToken) + { + try + { + var jobId = await _ccOpportunityClientJob.RefreshClientOpportunityTrackingDetails( + request.StrataId, request.OpportunityGuid, cancellationToken); + return Ok(jobId); + } + catch (InvalidOperationException ex) + { + _logger.LogServiceError(ex); + return BadRequest(); + } + } + + /// + /// Contains information required to request a charge code opportunity job. + /// + public class ChargeCodeOpportunityJobRequestInfo + { + public int StrataId { get; set; } + public Guid OpportunityGuid { get; set; } + } + + public class ChargeCodeDetailInfo + { + public int StrataId { get; set; } + public Guid OpportunityGuid { get; set; } + public bool IsBaseline { get; set; } + public bool IsUpdate { get; set; } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeOppsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeOppsController.cs new file mode 100644 index 0000000..273f8fd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ChargeCodeOppsController.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ChargeCodeOppsController : OpportunityController + { + + public ChargeCodeOppsController(ICCOpportunityViewService opportunityService) : base(opportunityService) + { + } + + [HttpPost("")] + public async Task> GetOpportunities(CCOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Export(CCOpportunityExportOptions exportOptions, CancellationToken cancellationToken) + { + var workbook = await ((ICCOpportunityViewService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken); + return new ExcelContentResult(workbook, "CC_Opportunities"); + + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await ((ICCOpportunityViewService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + [ExcludeFromCodeCoverage] + public class CCOpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public CCFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class CCOpportunityExportOptions + { + public SortOptions SortOptions { get; set; } + public CCFilters Filters { get; set; } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ClientNetworkOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ClientNetworkOpportunitiesController.cs new file mode 100644 index 0000000..23e8e3b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ClientNetworkOpportunitiesController.cs @@ -0,0 +1,175 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.Network.Filters; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ClientNetworkOpportunitiesController : ControllerBase + { + private readonly IClientNetworkOpportunityService _clientNetworkOpportunityService; + private readonly ILogger _logger; + private readonly ClaimsPrincipal _user; + + public ClientNetworkOpportunitiesController( + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IClientNetworkOpportunityService clientNetworkOpportunityService, + ILogger logger) + { + _clientNetworkOpportunityService = clientNetworkOpportunityService; + _logger = logger; + _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesDefaultResponseType] + public async Task>> GetOpportunities(NetworkOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid != Guid.Empty) + { + return await _clientNetworkOpportunityService.GetOpportunitiesAsync(databaseGuid, loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + return NoContent(); + } + + [HttpGet("{networkOpportunityId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesDefaultResponseType] + public async Task> GetById(int networkOpportunityId, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid != Guid.Empty) + { + try + { + var clientNetworkOpportunity = await _clientNetworkOpportunityService.GetByIdAsync(databaseGuid, networkOpportunityId, cancellationToken); + return new ClientNetworkOpportunityDto(clientNetworkOpportunity); + } + catch (CreateWorkbookFromTemplateException ex) + { + _logger.LogServiceError(ex); + return StatusCode(500, new { message = "Error creating client workbook. Verify template data source is a published standard data source with client permissions set." }); + } + } + + return NoContent(); + } + + [HttpPut("{networkOpportunityId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task Save(int networkOpportunityId, ClientNetworkOpportunityDto dto, CancellationToken cancellationToken) + { + if (networkOpportunityId != dto.NetworkOpportunityId) + { + return BadRequest(); + } + + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid != Guid.Empty) + { + ClientNetworkOpportunity clientOpportunity = dto.MappedtoClientOpportunity(); + NetworkOpportunitySaveModel networkOpportunity = dto.MappedtoNetworkOpportunitySaveModel(); + await _clientNetworkOpportunityService.ConfigureAsync(databaseGuid, networkOpportunityId, clientOpportunity, networkOpportunity, cancellationToken); + } + + return NoContent(); + } + + [HttpDelete("{networkOpportunityId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task Delete(int networkOpportunityId, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid != Guid.Empty) + { + await _clientNetworkOpportunityService.DeleteAsync(databaseGuid, networkOpportunityId, CancellationToken.None); + } + + return NoContent(); + } + + [HttpPut("{networkOpportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> Publish(int networkOpportunityId, bool isPublished, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid == Guid.Empty) + { + return BadRequest(); + } + var opportunity = await _clientNetworkOpportunityService.GetByIdAsync(databaseGuid, networkOpportunityId, cancellationToken); + if (opportunity is null) + { + return BadRequest(); + } + + return Ok(await _clientNetworkOpportunityService.PublishAsync(databaseGuid, opportunity, isPublished, cancellationToken)); + } + + [HttpPut("[action]/{networkOpportunityId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task UpdateInitiativeStatus(int networkOpportunityId, [FromBody] InitiativeStatuses status, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid != Guid.Empty) + { + await _clientNetworkOpportunityService.UpdateInitiativeStatusAsync(databaseGuid, networkOpportunityId, status, cancellationToken); + } + + return NoContent(); + } + + [HttpPut("{networkOpportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> RevertWorkbookToTemplate(int networkOpportunityId, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid == Guid.Empty) + { + return BadRequest(); + } + + var opportunity = await _clientNetworkOpportunityService.GetByIdAsync(databaseGuid, networkOpportunityId, cancellationToken); + if (opportunity is null) + { + return BadRequest(); + } + + return Ok(await _clientNetworkOpportunityService.RevertWorkbookToTemplateAsync(databaseGuid, opportunity, cancellationToken)); + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ClientNetworkOpportunityLinkController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ClientNetworkOpportunityLinkController.cs new file mode 100644 index 0000000..4746349 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ClientNetworkOpportunityLinkController.cs @@ -0,0 +1,205 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ClientNetworkOpportunityLinkController : Controller + { + private readonly IClientNetworkOpportunityService _clientNetworkOpportunityService; + private readonly IClientNetworkOpportunityLinkService _clientNetworkOpportunityLinkService; + private readonly IAllOpportunityService _allOpportunityService; + private readonly IInitiativeService _initiativeService; + private readonly ILogger _logger; + private readonly ClaimsPrincipal _user; + + public ClientNetworkOpportunityLinkController( + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IClientNetworkOpportunityService clientNetworkOpportunityService, + IClientNetworkOpportunityLinkService clientNetworkOpportunityLinkService, + IAllOpportunityService allOpportunityService, + IInitiativeService initiativeService, + ILogger logger) + { + _clientNetworkOpportunityService = clientNetworkOpportunityService; + _clientNetworkOpportunityLinkService = clientNetworkOpportunityLinkService; + _allOpportunityService = allOpportunityService; + _initiativeService = initiativeService; + _logger = logger; + _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + } + + [HttpPut] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> AddOpportunityLink([FromBody] CreateNetworkOpportunityLinkDto createNetworkOpportunityLink, CancellationToken cancellationToken) + { + if (createNetworkOpportunityLink.OpportunityGuid == Guid.Empty) { return BadRequest(); } + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid == Guid.Empty) { return BadRequest(); } + var cnoExists = await _clientNetworkOpportunityService.ContainsAsync(databaseGuid, createNetworkOpportunityLink.NetworkOpportunityId, cancellationToken); + if (!cnoExists) { return NotFound(); } + var opportunityExists = await _allOpportunityService.ContainsAsync(createNetworkOpportunityLink.OpportunityGuid, cancellationToken); + if (!opportunityExists) { return NotFound(); } + var link = await _clientNetworkOpportunityLinkService.AddOpportunityLinkAsync(databaseGuid, createNetworkOpportunityLink.NetworkOpportunityId, createNetworkOpportunityLink.OpportunityGuid, cancellationToken); + return Ok(new ClientNetworkOpportunityResultDto(link)); + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> RolloverOpportunityLink([FromBody] RolloverOpportunityDto rolloverOpportunityInfo, CancellationToken cancellationToken) + { + var databaseGuid = rolloverOpportunityInfo.DatabaseGuid ?? _user.GetDatabaseGuid(); + if (databaseGuid == Guid.Empty) { return BadRequest(); } + try + { + var link = await _clientNetworkOpportunityLinkService.RolloverOpportunityLinkAsync(databaseGuid, + rolloverOpportunityInfo.SourceConfigurationGuid, rolloverOpportunityInfo.SourceOpportunityGuid, + rolloverOpportunityInfo.RolloverConfigurationGuid, rolloverOpportunityInfo.RolloverOpportunityGuid, rolloverOpportunityInfo.RolloverInitiativeGuid, cancellationToken); + return Ok(new ClientNetworkOpportunityResultDto(link)); + } + catch (OpportunityNotFoundException nfe) + { + _logger.LogServiceError(nfe); + return NotFound(nfe.OpportunityGuid); + } + catch (ClientNetworkOpportunityLinkNotFoundException lnfe) + { + _logger.LogServiceError(lnfe); + return NotFound(new { lnfe.StrataId, lnfe.ConfigurationGuid, lnfe.OpportunityGuid }); + } + catch (ClientNetworkOpportunityLinkExistsException linkExists) + { + _logger.LogServiceError(linkExists); + return Ok(new ClientNetworkOpportunityResultDto(linkExists.ClientNetworkOpportunityLink)); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + return BadRequest(); + } + } + + [HttpDelete("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> DeleteOpportunityLink([FromBody] ClientNetworkOpportunityLinkDto opportunity, CancellationToken cancellationToken) + { + var databaseGuid = opportunity.DatabaseGuid ?? _user.GetDatabaseGuid(); + if (databaseGuid == Guid.Empty) { return BadRequest(); } + var cnoLinkExists = await _clientNetworkOpportunityLinkService.ContainsOpportunityLinkAsync(databaseGuid, opportunity.ConfigurationGuid, opportunity.Id, cancellationToken); + if (!cnoLinkExists) { return NotFound(); } + await _clientNetworkOpportunityLinkService.DeleteOpportunityLinkAsync(databaseGuid, opportunity.ConfigurationGuid, opportunity.Id, cancellationToken); + return Ok(); + } + + [HttpDelete("{opportunityGuid}/{configurationGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> DeleteOpportunityLinkIfExists(Guid opportunityGuid, Guid configurationGuid, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid == Guid.Empty) { return BadRequest(); } + + var cnoLinkExists = await _clientNetworkOpportunityLinkService.ContainsOpportunityLinkAsync(databaseGuid, configurationGuid, opportunityGuid, cancellationToken); + if (!cnoLinkExists) { return Ok(); } + await _clientNetworkOpportunityLinkService.DeleteOpportunityLinkAsync(databaseGuid, configurationGuid, opportunityGuid, cancellationToken); + return Ok(); + } + + [HttpDelete("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> DeleteInitiativeLink([FromBody] ClientNetworkOpportunityLinkDto initiative, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid == Guid.Empty) { return BadRequest(); } + var cnoLinkExists = await _clientNetworkOpportunityLinkService.ContainsInitiativeLinkAsync(databaseGuid, initiative.ConfigurationGuid, initiative.Id, cancellationToken); + if (!cnoLinkExists) { return NotFound(); } + await _clientNetworkOpportunityLinkService.DeleteInitiativeLinkAsync(databaseGuid, initiative.ConfigurationGuid, initiative.Id, cancellationToken); + return Ok(); + } + + [HttpDelete("{initiativeGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> DeleteInitiativeLink(Guid initiativeGuid, CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + if (databaseGuid == Guid.Empty) { return BadRequest(); } + + var initiative = await _initiativeService.GetById(initiativeGuid, cancellationToken); + var cnoLinkExists = await _clientNetworkOpportunityLinkService.ContainsInitiativeLinkAsync(databaseGuid, initiative.ConfigurationGUID, initiative.InitiativeGUID, cancellationToken); + if (!cnoLinkExists) { return NotFound(); } + await _clientNetworkOpportunityLinkService.DeleteInitiativeLinkAsync(databaseGuid, initiative.ConfigurationGUID, initiative.InitiativeGUID, cancellationToken); + return Ok(); + } + + public class RolloverOpportunityDto + { + public Guid? DatabaseGuid { get; set; } + public Guid SourceConfigurationGuid { get; set; } + public Guid SourceOpportunityGuid { get; set; } + public Guid RolloverConfigurationGuid { get; set; } + public Guid RolloverOpportunityGuid { get; set; } + public Guid RolloverInitiativeGuid { get; set; } + } + + public class ClientNetworkOpportunityLinkDto + { + public Guid? DatabaseGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public Guid Id { get; set; } + } + + public class CreateNetworkOpportunityLinkDto + { + public int NetworkOpportunityId { get; set; } + public Guid OpportunityGuid { get; set; } + } + + public class ClientNetworkOpportunityResultDto + { + public int NetworkOpportunityId { get; set; } + public Guid ConfigurationGuid { get; set; } + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public string Name { get; set; } + public ClientNetworkOpportunityResultDto(ClientNetworkOpportunityLink link, NetworkOpportunity networkOpportunity = default) + { + if (link == null) return; + NetworkOpportunityId = link.ClientNetworkOpportunity.NetworkOpportunityId; + ConfigurationGuid = link.ConfigurationGuid; + OpportunityGuid = link.OpportunityGuid; + InitiativeGuid = link.InitiativeGuid; + Name = link.ClientNetworkOpportunity.NetworkOpportunity?.Name ?? networkOpportunity?.Name ?? ""; + } + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ClientOpportunityTypeWorkbooksController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ClientOpportunityTypeWorkbooksController.cs new file mode 100644 index 0000000..197e37b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ClientOpportunityTypeWorkbooksController.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.Analytics.Models.Utils; +using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks; +using Strata.ContinuousImprovement.Biz.WorkbookClient; +using Strata.DecisionSupport.Models.Jazz; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ClientOpportunityTypeWorkbooksController : ControllerBase + { + private readonly IOpportunityTypeWorkbookService _opportunityTypeWorkbookService; + + public ClientOpportunityTypeWorkbooksController(IOpportunityTypeWorkbookService opportunityTypeWorkbookService) + { + _opportunityTypeWorkbookService = opportunityTypeWorkbookService; + + } + + [HttpGet("{opportunityType}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task>> GetOpportunityTypeWorkbooks([FromRoute] int opportunityType, CancellationToken cancellationToken) + { + var oppTypeWorkbooks = await _opportunityTypeWorkbookService.GetOpportunityTypeWorkbooksAsync(opportunityType, cancellationToken); + return Ok(oppTypeWorkbooks.Select(workbook => workbook.Mapped())); + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task>> CreateOpportunityTypeWorkbooks([FromBody] IEnumerable opportunityTypeWorkbooks, CancellationToken cancellationToken) + { + var createdOppTypeWorkbooks = await _opportunityTypeWorkbookService.CreateOpportunityTypeWorkbooksAsync(opportunityTypeWorkbooks, cancellationToken); + return CreatedAtAction(nameof(CreateOpportunityTypeWorkbooks), createdOppTypeWorkbooks); + } + + [HttpDelete("{opportunityType}/workbooks/{workbookId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task Delete([FromRoute] int opportunityType, [FromRoute] int workbookId, CancellationToken cancellationToken) + { + if (!await _opportunityTypeWorkbookService.ContainsAsync(opportunityType, workbookId, cancellationToken)) + { + return NotFound(); + } + + await _opportunityTypeWorkbookService.DeleteAsync(opportunityType, workbookId, cancellationToken); + return NoContent(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ClientOpportunityWorkbooksController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ClientOpportunityWorkbooksController.cs new file mode 100644 index 0000000..327666a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ClientOpportunityWorkbooksController.cs @@ -0,0 +1,68 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.Analytics.Models.Utils; +using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks; +using Strata.ContinuousImprovement.Biz.WorkbookClient; +using Strata.DecisionSupport.Models.Jazz; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ClientOpportunityWorkbooksController : ControllerBase + { + private readonly IOpportunityWorkbookService _opportunityWorkbookService; + + public ClientOpportunityWorkbooksController(IOpportunityWorkbookService opportunityWorkbookService) + { + _opportunityWorkbookService = opportunityWorkbookService; + } + + [HttpGet("workbooks")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task>> GetWorkbooks(CancellationToken cancellationToken) + { + var workbooks = await _opportunityWorkbookService.GetAllOpportunityWorkbooksAsync(false, cancellationToken); + return Ok(workbooks.Select(workbook => workbook.Mapped())); + } + + [HttpGet("{opportunityGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task>> GetOpportunityWorkbooks([FromRoute] Guid opportunityGuid, CancellationToken cancellationToken) + { + var oppWorkbooks = await _opportunityWorkbookService.GetOpportunityWorkbooksAsync(opportunityGuid, cancellationToken); + return Ok(oppWorkbooks.Select(workbook => workbook.Mapped())); + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task>> CreateOpportunityWorkbooks([FromBody] IEnumerable opportunityWorkbooks, CancellationToken cancellationToken) + { + var createdOppWorkbooks = await _opportunityWorkbookService.CreateOpportunityWorkbooksAsync(opportunityWorkbooks, cancellationToken); + return CreatedAtAction(nameof(CreateOpportunityWorkbooks), createdOppWorkbooks); + } + + [HttpDelete("{opportunityGuid}/workbooks/{workbookId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task Delete([FromRoute] Guid opportunityGuid, [FromRoute] int workbookId, CancellationToken cancellationToken) + { + if (!await _opportunityWorkbookService.ContainsAsync(opportunityGuid, workbookId, cancellationToken)) + { + return NotFound(); + } + + await _opportunityWorkbookService.DeleteAsync(opportunityGuid, workbookId, cancellationToken); + return NoContent(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ClientWarehousesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ClientWarehousesController.cs new file mode 100644 index 0000000..b1b8730 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ClientWarehousesController.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.ClientWarehouses; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [Route("api/v{api-version:apiVersion}/[controller]")] + [ApiController] + public class ClientWarehousesController(IClientWarehouseService clientWarehouseService) : ControllerBase + { + [HttpGet] + [Authorize(Policy = Policies.StrataOnly)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesDefaultResponseType] + public async Task>> GetClientWarehouses(CancellationToken cancellationToken) + { + var clientWarehouses = await clientWarehouseService.GetAll(cancellationToken); + if (clientWarehouses.Count != 0) + { + return Ok(clientWarehouses); + } + + return NoContent(); + } + + [HttpPost] + [Authorize(Policy = Policies.StrataOnly)] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> AddClientWarehouse([FromBody] ClientWarehouse clientWarehouse, CancellationToken cancellationToken) + { + return CreatedAtAction(nameof(AddClientWarehouse), await clientWarehouseService.Add(clientWarehouse, cancellationToken)); + } + + [HttpDelete("{databaseGuid}")] + [Authorize(Policy = Policies.StrataOnly)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task DeleteClientWarehouse([FromRoute] Guid databaseGuid, CancellationToken cancellationToken) + { + await clientWarehouseService.Delete(databaseGuid, cancellationToken); + return NoContent(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/CommentsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/CommentsController.cs new file mode 100644 index 0000000..3d847fd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/CommentsController.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.OpportunityComments; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class CommentsController : ControllerBase + { + private readonly ICommentService _commentService; + private readonly IClaimsPrincipalAccessor _claimsPrincipalAccessor; + + public CommentsController(ICommentService commentService, + IClaimsPrincipalAccessor claimsPrincipalAccessor) + { + _commentService = commentService; + _claimsPrincipalAccessor = claimsPrincipalAccessor; + } + + [HttpPost("")] + public async Task> GetComments(CommentOptions options, CancellationToken cancellationToken) + { + var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + var databaseGuid = options.IsGlobal ? Guid.Empty : user?.GetDatabaseGuid() ?? Guid.Empty; + var opportunityType = int.Parse(options.OpportunityType); + return await _commentService.GetCommentsAsync(databaseGuid, opportunityType, options.OpportunityId, cancellationToken); + } + + [HttpPost("[action]")] + public async Task Create(NewComment comment, CancellationToken cancellationToken) + { + var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + if (user == null || string.IsNullOrEmpty(comment.Comment)) { return default; } + var databaseGuid = comment.IsGlobal ? Guid.Empty : user.GetDatabaseGuid(); + var author = $"{user.GetUserLastName()}, {user.GetUserFirstName()}"; + var opportunityType = int.Parse(comment.OpportunityType); + return await _commentService.AddCommentAsync(databaseGuid, opportunityType, comment.OpportunityId, comment.Comment, comment.CommentDate, author, cancellationToken); + } + + public class CommentOptions + { + public bool IsGlobal { get; set; } + public string OpportunityType { get; set; } + public long OpportunityId { get; set; } + } + + public class NewComment : CommentOptions + { + public string Comment { get; set; } + public DateTimeOffset? CommentDate { get; set; } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ConfigurationsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ConfigurationsController.cs new file mode 100644 index 0000000..073d03d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ConfigurationsController.cs @@ -0,0 +1,131 @@ +using Microsoft.AspNetCore.Mvc; +using MoreLinq; +using Pluralize.NET; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.Schema.Client; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ConfigurationsController : ControllerBase + { + private readonly IConfigurationService _configurationService; + private readonly ISchemaServiceClient _schemaServiceClient; + + public ConfigurationsController(IConfigurationService configurationService, ISchemaServiceClient schemaServiceClient) + + { + _configurationService = configurationService; + _schemaServiceClient = schemaServiceClient; + } + + [HttpGet("")] + public async Task> GetAllConfigurations(CancellationToken cancellationToken) + { + var configurations = await _configurationService.GetConfigurationsAsync(cancellationToken); + return configurations.OrderBy(c => c.DisplayOrder); + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task> GetConfigurationEntities(Guid configurationGuid, CancellationToken cancellationToken) + { + var configuration = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + + var globalId = configuration.IsUsingCustomEntityDimension ? configuration.DepartmentRollupGlobalId : "Entity"; + var members = await _schemaServiceClient.GetDimensionMembersByGlobalIdAsync(globalId); + + var configuredEntitiesLookup = new HashSet(configuration.ConfiguredEntityIds); + var configuredMembers = members.Where(member => configuredEntitiesLookup.Contains(member.Id)); + + return configuredMembers; + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetConfigurationEntityInfo(Guid configurationGuid, CancellationToken cancellationToken) + { + var configuration = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + + var globalId = configuration.IsUsingCustomEntityDimension ? configuration.DepartmentRollupGlobalId : "Entity"; + var entity = await _schemaServiceClient.GetDimensionByGlobalIdAsync(globalId); + var pluralize = new Pluralizer(); + var entityInfo = new EntityInfo + { + EntityTitle = pluralize.Pluralize(entity.FriendlyName), + EntityLabel = entity.FriendlyName, + EntityText = entity.FriendlyName, + DepartmentRollupGlobalId = globalId, + HierarchicalGuid = entity.DefaultHierarchyGuid.ToString() + }; + return entityInfo; + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetConfigurationServiceLineInfo(Guid configurationGuid, CancellationToken cancellationToken) + { + var configuration = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + var dimensionGuid = configuration.ServiceLineDimensionGuid; + Schema.Client.Dtos.Info.ScoreDimensionInfoDto serviceLine = null; + + if (dimensionGuid == Guid.Empty) + { + serviceLine = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Service Line"); + } + else + { + try + { + serviceLine = await _schemaServiceClient.GetDimensionByIdAsync(dimensionGuid); + } + catch + { + // empty to ensure code will fall thru to next block + } + if (serviceLine is null) + { + serviceLine = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Service Line"); + } + + } + + var pluralize = new Pluralizer(); + var serviceLineInfo = new ServiceLineInfo + { + ServiceLineGlobalId = string.Empty, + ServiceLineDimensionGuid = dimensionGuid.ToString(), + ServiceLineHierarchicalGuid = serviceLine.DefaultHierarchyGuid.ToString(), + ServiceLineName = serviceLine.FriendlyName, + ServiceLineTitle = pluralize.Pluralize(serviceLine.FriendlyName) + }; + return serviceLineInfo; + } + + } + + [ExcludeFromCodeCoverage] + public class EntityInfo + { + public string EntityTitle { get; set; } + public string EntityLabel { get; set; } + public string EntityText { get; set; } + public string DepartmentRollupGlobalId { get; set; } + public string HierarchicalGuid { get; set; } + } + + [ExcludeFromCodeCoverage] + public class ServiceLineInfo + { + public string ServiceLineGlobalId { get; set; } + public string ServiceLineTitle { get; set; } + public string ServiceLineName { get; set; } + public string ServiceLineDimensionGuid { get; set; } + public string ServiceLineHierarchicalGuid { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/DatabasesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/DatabasesController.cs new file mode 100644 index 0000000..0ed184e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/DatabasesController.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.Databases; +using Strata.ContinuousImprovement.Biz.Replication; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [Route("api/v{api-version:apiVersion}/[controller]")] + [ApiController] + public class DatabasesController : ControllerBase + { + private readonly IDatabaseService _databaseService; + + public DatabasesController(IDatabaseService databaseService) + { + _databaseService = databaseService; + } + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesDefaultResponseType] + public async Task>> Get(CancellationToken cancellationToken) + { + var databases = await _databaseService.GetDatabasesAsync(cancellationToken); + if (databases.Count != 0) + { + return Ok(databases); + } + return NoContent(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/DeviceIdentifiersController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/DeviceIdentifiersController.cs new file mode 100644 index 0000000..ab8d311 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/DeviceIdentifiersController.cs @@ -0,0 +1,33 @@ +using Hangfire; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.GMDN.Jobs; +using System.Threading; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + [Authorize(Policy = Policies.StrataOnly)] + public class DeviceIdentifiersController(IBackgroundJobClient backgroundJobClient) : ControllerBase + { + private readonly IBackgroundJobClient _backgroundJobClient = backgroundJobClient; + + /// + /// Clears device identifier data from all non-licensed CI clients + /// + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public ActionResult ClearDeviceIdentifiers(CancellationToken cancellationToken) + { + var jobId = _backgroundJobClient.Enqueue(job + => job.ClearDimDeviceIdentifierFromJazz(cancellationToken)); + + return Ok(jobId); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/EncounterOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/EncounterOpportunitiesController.cs new file mode 100644 index 0000000..e326090 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/EncounterOpportunitiesController.cs @@ -0,0 +1,216 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Encounter.Initiatives; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.OrgDefined; +using Strata.ContinuousImprovement.Biz.OrgDefined.Queries; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class EncounterOpportunitiesController : OpportunityController + { + private readonly IClaimsPrincipalAccessor _claimsPrincipalAccessor; + private readonly IConfigurationService _configurationService; + private readonly IENOpportunityService _opportunityService; + private readonly IENInitiativeService _initiativeService; + private readonly IOpportunityMetricCohortService _opportunityMetricCohortService; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + private readonly IAllOpportunityService _allOpportunityService; + + public EncounterOpportunitiesController( + IConfigurationService configurationService, + IENOpportunityService opportunityService, + IENInitiativeService initiativeService, + IOpportunityMetricCohortService opportunityMetricCohortService, + IFeatureFlagServiceClient featureFlagServiceClient, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IAllOpportunityService allOpportunityService) + : base(opportunityService) + { + _configurationService = configurationService; + _opportunityService = opportunityService; + _initiativeService = initiativeService; + _opportunityMetricCohortService = opportunityMetricCohortService; + _featureFlagServiceClient = featureFlagServiceClient; + _claimsPrincipalAccessor = claimsPrincipalAccessor; + _allOpportunityService = allOpportunityService; + } + + [HttpPost("")] + public async Task> GetOpportunities(ENOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> Create(ENOpportunityDto opportunity, CancellationToken cancellationToken) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + var opportunityWithFilters = opportunity.MapToDataModel(User); + await ((IENOpportunityService)OpportunityService).SetOpportunityFilters(opportunityWithFilters, cancellationToken); + var newOpportunity = opportunityWithFilters.MapToDataModel(User); + var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + if (dbGuid == Guid.Empty) + { + return BadRequest(); + } + var isTempoPage = await _featureFlagServiceClient.IsEnabledAsync("enableencounteropportunityintempo", dbGuid, cancellationToken: cancellationToken); + + var result = await ((IENOpportunityService)OpportunityService).CreateAsync(newOpportunity, dbGuid, isTempoPage, cancellationToken); + return Ok(new OpportunityJobResultsDto(result, newOpportunity.OpportunityGuid)); + } + + [HttpDelete("{id}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task Delete(Guid id, CancellationToken cancellationToken) + => await OpportunityService.DeleteAsync(id, cancellationToken); + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Export(ENOpportunityExportOptions exportOptions, CancellationToken cancellationToken) + { + var workbook = await ((IENOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken); + return new ExcelContentResult(workbook, "EN_Opportunities"); + + } + + [HttpGet("{opportunityGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetById(Guid opportunityGuid, CancellationToken cancellationToken) + { + var opportunityDto = await _opportunityService.GetENOpportunityById(opportunityGuid, cancellationToken); + if (opportunityDto == null) + { + return NotFound(); + } + + return Ok(opportunityDto); + } + + [HttpPut("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> UpdateGoals(Guid opportunityGuid, [FromBody] IEnumerable changes, CancellationToken cancellationToken) + { + var existsOpportunity = await _opportunityService.Contains(opportunityGuid, cancellationToken); + if (!existsOpportunity) + { + return NotFound(); + } + + var hasUpdatedCostPerUnit = await _opportunityService.UpdateGoalsAsync(opportunityGuid, changes, cancellationToken); + + if (hasUpdatedCostPerUnit) + { + return await _initiativeService.UpdateSavings(opportunityGuid, cancellationToken); + } + + return false; + } + + [HttpGet("{opportunityGuid}/[action]")] + public async Task GetEditModel(Guid opportunityGuid, CancellationToken cancellationToken) + { + var opportunity = await _opportunityService.GetENOpportunityById(opportunityGuid, cancellationToken); + return opportunity.MapToSaveModel(); + } + + [HttpGet("{opportunityGuid}/[action]")] + public async Task GetInitiativeInfo(Guid opportunityGuid, CancellationToken cancellationToken) + { + return await _opportunityService.GetInitiativeInfoAsync(opportunityGuid, cancellationToken); + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await ((IENOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + [HttpGet("[action]")] + public async Task TempoPageEnabled(CancellationToken cancellationToken) + => await _featureFlagServiceClient.IsEnabledAsync("enableencounteropportunityintempo", User?.GetDatabaseGuid() ?? Guid.Empty, cancellationToken: cancellationToken); + + [HttpPut("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> UpdateOpportunity(Guid opportunityGuid, [FromBody] ENOpportunityDto opportunityDto, CancellationToken cancellationToken) + { + var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + var isNameValid = await _allOpportunityService.NameIsValid(opportunityDto.ConfigurationGuid, (int)OpportunityTypes.Encounter, opportunityDto.Name, + opportunityDto?.OpportunityGuid, cancellationToken); + if (dbGuid == Guid.Empty || !isNameValid) + { + return BadRequest(); + } + var opportunityWithFilters = opportunityDto.MapToDataModel(User, false); + var result = await ((IENOpportunityService)OpportunityService).UpdateAsync(dbGuid, opportunityWithFilters, cancellationToken); + return Ok(result); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> OpportunityMetricCohortData([FromBody] OpportunityMetricCohortOptions options, CancellationToken cancellationToken) + { + var config = await _configurationService.GetConfigurationAsync(options.ConfigurationGuid, cancellationToken); + if (config == null) + { + return BadRequest(); + } + var result = Enumerable.Empty(); + result = await _opportunityMetricCohortService.GetEncounterOpportunityMetricCohortData(config, options.Data, options.InnerJoin, cancellationToken); + return Ok(result); + } + } + + [ExcludeFromCodeCoverage] + public class ENOpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public ENFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class ENOpportunityExportOptions + { + public SortOptions SortOptions { get; set; } + public ENFilters Filters { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ExplorationController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ExplorationController.cs new file mode 100644 index 0000000..21cfa6f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ExplorationController.cs @@ -0,0 +1,129 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.Exploration; +using Strata.ContinuousImprovement.Biz.Exploration.Benchmark; +using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ExplorationController : ControllerBase + { + private readonly IExplorationService _explorationService; + private readonly IConfigurationService _configurationService; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + private readonly ILogger _logger; + + public ExplorationController(IExplorationService explorationService, + IConfigurationService configurationService, + IFeatureFlagServiceClient featureFlagServiceClient, + ILogger logger) + { + _explorationService = explorationService; + _configurationService = configurationService; + _featureFlagServiceClient = featureFlagServiceClient; + _logger = logger; + } + + [HttpPost("case-type-family-summary")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetCaseTypeFamilySummaries([FromBody] ExplorationInfo explorationInfo, CancellationToken cancellationToken) + { + Stopwatch performanceTimer = null; + var isFlexExPerfLoggingEnabled = await _explorationService.IsFlexExPerfLogging(cancellationToken); + + if (isFlexExPerfLoggingEnabled) + { + _logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilySummaries Controller Entered"); + performanceTimer = Stopwatch.StartNew(); + } + + if (!await _configurationService.IsConfigurationValid(explorationInfo.ConfigurationGuid, cancellationToken)) { return BadRequest(); } + var summaries = await _explorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, cancellationToken); + if (isFlexExPerfLoggingEnabled) _logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilySummaries Controller * Total Elapsed time: {performanceTimer.Elapsed}"); + + return Ok(summaries); + } + + [HttpPost("case-type-family-cost-driver-info")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetCaseTypeFamilyCostDriverInfo([FromBody] ExplorationCostDriverInfo explorationCostDriverInfo, CancellationToken cancellationToken) + { + Stopwatch performanceTimer = null; + var isFlexExPerfLoggingEnabled = await _explorationService.IsFlexExPerfLogging(cancellationToken); + + if (isFlexExPerfLoggingEnabled) + { + _logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilyCostDriverInfo Controller Entered"); + performanceTimer = Stopwatch.StartNew(); + } + + if (!await _configurationService.IsConfigurationValid(explorationCostDriverInfo.ConfigurationGuid, cancellationToken)) { return BadRequest(); } + var summaries = await _explorationService.GetCaseTypeFamilySummaryDetailsAsync(explorationCostDriverInfo, cancellationToken); + if (isFlexExPerfLoggingEnabled) _logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilyCostDriverInfo Controller * Total Elapsed time: {performanceTimer.Elapsed}"); + + return Ok(summaries); + } + + [HttpPost("case-type-family-details")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> GetCaseTypeFamilyDetails([FromBody] ExplorationDetailInfo explorationInfo, CancellationToken cancellationToken) + { + Stopwatch performanceTimer = null; + var isFlexExPerfLoggingEnabled = await _explorationService.IsFlexExPerfLogging(cancellationToken); + + if (isFlexExPerfLoggingEnabled) + { + _logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilyDetails Controller Entered"); + performanceTimer = Stopwatch.StartNew(); + } + + if (!await _configurationService.IsConfigurationValid(explorationInfo.ConfigurationGuid, cancellationToken)) { return BadRequest(); } + await _configurationService.GetQualityVariationAprdrgFlagAsync(explorationInfo.ConfigurationGuid, cancellationToken); + var ctfEntityDetails = await _explorationService.GetCaseTypeFamilyDetailsAsync(explorationInfo, cancellationToken); + if (isFlexExPerfLoggingEnabled) _logger.LogPerformanceInformation($"CI FlexEx Perf * GetCaseTypeFamilyDetails Controller * Total Elapsed time: {performanceTimer.Elapsed}"); + + return Ok(ctfEntityDetails); + } + + [HttpPost("case-type-family-summary-heatmap")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetHeatMapSummary([FromBody] ExplorationInfo explorationInfo, CancellationToken cancellationToken) + { + if (!await _configurationService.IsConfigurationValid(explorationInfo.ConfigurationGuid, cancellationToken)) { return BadRequest(); } + var summaries = await _explorationService.GetBenchmarkingDataForHeatMapAsync(explorationInfo, explorationInfo.Granularity, cancellationToken); + return Ok(summaries); + } + + [HttpGet("flexible-exploration-information-page")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> IsFlexibleExplorationInformationPageEnabled(CancellationToken cancellationToken) + { + var databaseGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + var isExplorationInformationPageEnable = await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationpage3enabled", databaseGuid, cancellationToken: cancellationToken); + return Ok(isExplorationInformationPageEnable); + } + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ExplorationOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ExplorationOpportunitiesController.cs new file mode 100644 index 0000000..326f82e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ExplorationOpportunitiesController.cs @@ -0,0 +1,179 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Exploration.Opportunities; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.OpportunityComments; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ExplorationOpportunitiesController : OpportunityController + { + private readonly IEXOpportunityService _opportunityService; + private readonly ICommentService _commentService; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + private readonly ILogger _logger; + + public ExplorationOpportunitiesController(IEXOpportunityService opportunityService, + ICommentService commentService, + IFeatureFlagServiceClient featureFlagServiceClient, + ILogger logger) : base(opportunityService) + { + _opportunityService = opportunityService; + _commentService = commentService; + _featureFlagServiceClient = featureFlagServiceClient; + _logger = logger; + } + + [HttpGet("[action]")] + public async Task GetEnabled(CancellationToken cancellationToken) + { + var databaseGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + var hasFlexibleExplorartion = User.IsSdtEmployee() + || (await _featureFlagServiceClient.IsEnabledAsync("ciflexibleexplorationenabled", databaseGuid, cancellationToken: cancellationToken)); + var dict = new Dictionary { { "isSdtEmployee", User.IsSdtEmployee() }, { "hasFlexibleExploration", hasFlexibleExplorartion } }; + _logger.LogServiceInformation("CI Flexible Exploration Enabled", dict); + return hasFlexibleExplorartion; + } + + [HttpPost("")] + public async Task> GetOpportunities(EXOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpGet("{opportunityGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesDefaultResponseType] + public async Task> GetById(Guid opportunityGuid, CancellationToken cancellationToken) + { + return Ok(await _opportunityService.GetById(opportunityGuid, cancellationToken)); + } + + [HttpPut("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesDefaultResponseType] + public async Task CheckWorkbook(Guid opportunityGuid, CancellationToken cancellationToken) + => await _opportunityService.CheckWorkbook(opportunityGuid, cancellationToken); + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> Create(EXOpportunityDto savedOpportunity, CancellationToken cancellationToken) + { + var newOpportunity = new EXOpportunity + { + ConfigurationGuid = savedOpportunity.ConfigurationGuid, + Name = savedOpportunity.Name, + Description = savedOpportunity.Description + }; + + return Ok(await _opportunityService.CreateAsync(newOpportunity, User?.Identity?.Name, cancellationToken)); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Export(EXOpportunityExportOptions exportOptions, CancellationToken cancellationToken) + { + var workbook = await ((IEXOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken); + return new ExcelContentResult(workbook, "EX_Opportunities"); + + } + + [HttpPut("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> Update(Guid opportunityGuid, EXOpportunity opportunity, CancellationToken cancellationToken) + { + var opp = await _opportunityService.GetById(opportunityGuid, cancellationToken); + if (opp == null) + { + return BadRequest(); + } + return Ok(await _opportunityService.UpdateAsync(opportunity, cancellationToken)); + } + + [HttpPut("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> SetStatus(Guid opportunityGuid, [FromBody] InitiativeStatuses status, CancellationToken cancellationToken) + { + var opp = await _opportunityService.GetById(opportunityGuid, cancellationToken); + if (opp == null) + { + return BadRequest(); + } + opp.InitiativeStatus = status; + return Ok(await _opportunityService.UpdateAsync(opp, cancellationToken)); + } + + [HttpDelete("{opportunityGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task Delete(Guid opportunityGuid, CancellationToken cancellationToken) + { + var opp = await _opportunityService.GetById(opportunityGuid, cancellationToken); + if (opp == null) + { + return BadRequest(); + } + await ((IEXOpportunityService)_opportunityService).DeleteAsync(opportunityGuid, cancellationToken); + var databaseGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + await _commentService.DeleteCommentsAsync(databaseGuid, (int)OpportunityTypes.Blank, opp.OpportunityId, cancellationToken); + return Ok(); + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await ((IEXOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + public class EXOpportunityDto + { + [Required] + [StringLength(200)] + public string Name { get; set; } + public string Description { get; set; } + public Guid ConfigurationGuid { get; set; } + } + + [ExcludeFromCodeCoverage] + public class EXOpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public EXFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class EXOpportunityExportOptions + { + public SortOptions SortOptions { get; set; } + public EXFilters Filters { get; set; } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ExplorationPopulationsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ExplorationPopulationsController.cs new file mode 100644 index 0000000..0bd9688 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ExplorationPopulationsController.cs @@ -0,0 +1,241 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Exploration; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Population; +using Strata.ContinuousImprovement.Biz.UserSetting; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims.Extensions; +using Strata.DecisionSupport.Models.Encounters; +using Strata.DecisionSupport.Models.Jazz; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ExplorationPopulationsController : ControllerBase + { + private readonly IUserSettingService _userSettingService; + private readonly IExplorationPopulationService _populationService; + private readonly IExplorationService _explorationService; + private readonly ILogger _logger; + public ExplorationPopulationsController(IUserSettingService userSettingService, IExplorationPopulationService populationService, IExplorationService explorationService, + ILogger logger) + { + _userSettingService = userSettingService; + _populationService = populationService; + _explorationService = explorationService; + _logger = logger; + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task IsNameUnique([FromBody] string name, CancellationToken cancellationToken) + { + var isValid = await _populationService.IsNameUnique(name, cancellationToken); + return isValid; + } + + [HttpPost("")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> AddPopulation([FromBody] ExplorationPopulationDto populationDto, CancellationToken cancellationToken) + { + var isAdded = await _populationService.AddPopulationAsync(populationDto, cancellationToken); + return Ok(isAdded); + } + + [HttpGet("{populationId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task>> GetDischargeDates(long populationId, CancellationToken cancellationToken) + { + var population = await _populationService.ContainsPopulation(populationId, cancellationToken); + if (!population) return NotFound(); + + var result = await _populationService.GetDischargeDates(populationId, cancellationToken); + return Ok(new Tuple(result.Item1, result.Item2)); + } + + [HttpPut("{populationId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task UpdatePopulation(long populationId, ExplorationPopulationDto populationDto, CancellationToken cancellationToken) + { + var population = await _populationService.ContainsPopulation(populationId, cancellationToken); + if (!population) return NotFound(); + + var isUpdated = await _populationService.UpdatePopulationAsync(populationDto, cancellationToken); + + if (isUpdated) + { + var counts = await _populationService.GetPopulationCountOpportunitiesAsync([populationId], cancellationToken); + if (counts.TryGetValue(populationId, out int opportunitiesCount) && opportunitiesCount > 0) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + var userSetting = await _userSettingService.GetById(userGuid, cancellationToken); + if (userSetting != null) + { + await _populationService.CreateExplorationPopulationOpportunity(userSetting.ConfigurationGuid, populationId, false, cancellationToken); + } + } + return NoContent(); + } + return BadRequest(); + } + + [HttpGet("")] + public async Task>> GetPopulations(CancellationToken cancellationToken) + { + var result = await _populationService.GetPopulationsAsync(cancellationToken); + return Ok(result); + } + + [HttpPut("{populationId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> UpdatePopulationName(long populationId,[FromBody] string name, CancellationToken cancellationToken) + { + var isUpdated = await _populationService.UpdatePopulationName(populationId, name, cancellationToken); + return Ok(isUpdated); + } + + [HttpGet("{populationId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetPopulationData(long populationId, CancellationToken cancellationToken) + { + var population = await _populationService.ContainsPopulation(populationId, cancellationToken); + if (!population) return NotFound(); + + var result = await _populationService.GetPopulationDataAsync(populationId, cancellationToken); + return result != null ? Ok(result) : BadRequest(); + } + + [HttpGet("{populationId}/details")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> GetPopulationDetails(long populationId, CancellationToken cancellationToken) + { + var result = await _populationService.GetPopulationDetailsAsync(populationId, cancellationToken); + if (result == null) + { + return BadRequest(); + } + return Ok(result); + } + + [HttpGet("info/{populationId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> GetPopulationInfo(long populationId, CancellationToken cancellationToken) + { + var result = await _populationService.GetPopulationInfoAsync(populationId, cancellationToken); + if (result == null) + { + return BadRequest(); + } + return Ok(result); + } + + [HttpGet("{populationId}/encounters")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetEncounters(long populationId, Guid configurationGuid, CancellationToken cancellationToken) + { + _logger.LogServiceInformation($"Performing {nameof(GetEncounters)} for configuration: {configurationGuid}, populationid: {populationId}"); + + var populationData = await _populationService.GetPopulationDataAsync(populationId, cancellationToken); + if (populationData != null) + { + _logger.LogServiceInformation($"{nameof(GetEncounters)} populationData is not null, totalEncounters:{populationData.TotalEncounters}"); + + var encounterPhysicians = await _explorationService.GetEncountersAsync(populationData.MapToExplorationDetailInfo(), configurationGuid, cancellationToken); + if (encounterPhysicians != null) + { + if (configurationGuid == Guid.Empty) + { + var newIncludedEncounterIds = encounterPhysicians.Where(e => !e.IsExcluded).Select(x => x.EncounterId); + var newExcludedEncounterIds = encounterPhysicians.Where(e => e.IsExcluded).Select(x => x.EncounterId); + var updated = await _populationService.UpdatePopulationMappingsAsync(populationId, newIncludedEncounterIds, newExcludedEncounterIds, cancellationToken); + } + + var result = encounterPhysicians.Where(enc => !enc.IsExcluded).Select(x => x.MapToExplorationEncounterInfo()); + return Ok(result); + } + } + else + { + _logger.LogServiceInformation($"{nameof(GetEncounters)} populationData is null"); + } + + _logger.LogServiceInformation($"{nameof(GetEncounters)} returned a BadRequest()"); + return BadRequest(); + } + + [HttpDelete("{populationId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + [Authorize(Policy = Policies.IsAdmin)] + public async Task DeletePopulation(long populationId, CancellationToken cancellationToken) + { + var population = await _populationService.ContainsPopulation(populationId, cancellationToken); + if (!population) return NotFound(); + + var result = await _populationService.DeletePopulation(populationId, cancellationToken); + return result ? NoContent() : BadRequest(); + } + + [HttpPost("{populationId}/duplicate")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> DuplicatePopulation(long populationId, [FromBody] string name, CancellationToken cancellationToken) + { + var populationData = await _populationService.DuplicatePopulation(populationId, name, cancellationToken); + return populationData != null ? Ok(populationData) : BadRequest(); + } + + [HttpPost("{populationId}/opportunities")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> CreateOpportunity(long populationId, CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + var userSetting = await _userSettingService.GetById(userGuid, cancellationToken); + if (userSetting != null) + { + var result = await _populationService.CreateExplorationPopulationOpportunity(userSetting.ConfigurationGuid, populationId, true, cancellationToken); + if (result) + { + var opportunity = (await _populationService.GetPopulationOpportunitiesAsync([populationId], cancellationToken)).FirstOrDefault()?.FirstOrDefault(); + return Ok(opportunity); + } + } + + return BadRequest(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/FiltersController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/FiltersController.cs new file mode 100644 index 0000000..0f7d6c1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/FiltersController.cs @@ -0,0 +1,60 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.ChargeCode; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [Route("api/v{api-version:apiVersion}/[controller]")] + [ApiController] + public class FiltersController : ControllerBase + { + private readonly IChargeCodeService _chargeCodeService; + private readonly IFilterService _filterService; + + public FiltersController(IChargeCodeService chargeCodeService, IFilterService filterService) + { + _chargeCodeService = chargeCodeService; + _filterService = filterService; + } + + [HttpGet("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> GetCostDriverFilters(CancellationToken cancellationToken) + { + var costDrivers = await _chargeCodeService.GetChargeCodeCostDrivers(cancellationToken); + return costDrivers.Select(costDriver => new FilterListItem { Key = costDriver, Title = costDriver }).ToList(); + } + + [HttpGet("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> GetSupplyFilters(CancellationToken cancellationToken) + { + return await _filterService.GetSupplyList(cancellationToken); + } + + [HttpGet("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> GetCostComponentFilters(CancellationToken cancellationToken) + { + return await _filterService.GetCostComponentList(cancellationToken); + } + + + [HttpGet("[action]/{chipKey}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> GetSphCriteriaFilters([FromRoute] ChipKey chipKey, CancellationToken cancellationToken) + { + return await _filterService.GetSphCriteriaList(chipKey, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/FreeFormOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/FreeFormOpportunitiesController.cs new file mode 100644 index 0000000..29d8262 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/FreeFormOpportunitiesController.cs @@ -0,0 +1,131 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives; +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities; +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class FreeFormOpportunitiesController : OpportunityController + { + private readonly IInitiativeService _initiativeService; + private readonly IFFOpportunityService _ffOpportunityService; + + public FreeFormOpportunitiesController(IFFOpportunityService opportunityService, + IInitiativeService initiativeService) : base(opportunityService) + { + _initiativeService = initiativeService; + _ffOpportunityService = opportunityService; + } + + [HttpPost("")] + public async Task> GetOpportunities(FFOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + => await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + + [HttpGet("{opportunityGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetById(Guid opportunityGuid, CancellationToken cancellationToken) + { + var dataModel = await _ffOpportunityService.GetDataModelById(opportunityGuid, cancellationToken); + var detailModel = dataModel.MapToDetailModel(dataModel.Configuration.FiscalMonthResolver); + return Ok(detailModel); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> Create(FFOpportunityDto opportunity, CancellationToken cancellationToken) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + var newOpportunity = opportunity.MapToDataModel(); + newOpportunity.AuthorGuid = User.GetUserGuid(); + newOpportunity.AuthorName = User.Identity.Name; + + var opportunityGuid = await ((IFFOpportunityService)OpportunityService).CreateAsync(newOpportunity, cancellationToken); + return Ok(opportunityGuid); + } + + [HttpDelete("{id}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task Delete(Guid id, CancellationToken cancellationToken) + => await OpportunityService.DeleteAsync(id, cancellationToken); + + [HttpGet("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetInitiative(Guid opportunityGuid, CancellationToken cancellationToken) + { + var opportunity = await ((IFFOpportunityService)OpportunityService).GetDataModelById(opportunityGuid, cancellationToken); + if (opportunity == null) { return BadRequest(); } + var initiative = await _initiativeService.GetById(opportunity.InitiativeGuid, cancellationToken); + return initiative.MapToFreeFormInitiative(opportunity); + } + + [HttpPost("{opportunityGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task SetGoalsAsync(Guid opportunityGuid, [FromBody] FFOpportunityMonthValuesDto dto, CancellationToken cancellationToken) + => await ((IFFOpportunityService)OpportunityService).SetGoalsAsync(opportunityGuid, dto.OpportunityDetail, dto.MonthValues, cancellationToken); + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Export(FFOpportunityExportOptions exportOptions, CancellationToken cancellationToken) + { + var workbook = await ((IFFOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken); + return new ExcelContentResult(workbook, "FF_Opportunities"); + + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + => await ((IFFOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken); + + } + + [ExcludeFromCodeCoverage] + public class FFOpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public FFFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class FFOpportunityExportOptions + { + public SortOptions SortOptions { get; set; } + public FFFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class FFOpportunityMonthValuesDto + { + public FFOpportunityDetailModel OpportunityDetail { get; set; } + public IList MonthValues { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/GMDNFileUploadController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/GMDNFileUploadController.cs new file mode 100644 index 0000000..1b19727 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/GMDNFileUploadController.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.GMDN; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class GMDNFileUploadController : ControllerBase + { + private readonly IGMDNService _gmdnService; + + public GMDNFileUploadController(IGMDNService gmdnService) + { + _gmdnService = gmdnService; + } + + [HttpPost("{fileType}/[action]")] + [DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = int.MaxValue, ValueLengthLimit = int.MaxValue)] + public async Task UploadGMDNFile(GMDNFileType fileType, IFormFile file, CancellationToken cancellationToken) + { + var userName = User?.Identity?.Name; + switch (fileType) + { + case GMDNFileType.Term: + await _gmdnService.SeedGMDNTerms(file, userName, cancellationToken); + break; + case GMDNFileType.TermCategory: + await _gmdnService.SeedGMDNTermCategories(file, userName, cancellationToken); + break; + case GMDNFileType.Category: + await _gmdnService.SeedGMDNCategories(file, userName, cancellationToken); + break; + } + return Ok(); + } + } + + public enum GMDNFileType + { + Term, + TermCategory, + Category + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/GeneralLedgerDetailsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/GeneralLedgerDetailsController.cs new file mode 100644 index 0000000..7be485d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/GeneralLedgerDetailsController.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Details; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class GeneralLedgerDetailsController : ControllerBase + { + private readonly IGLDetailService _glDetailService; + + public GeneralLedgerDetailsController(IGLDetailService glDetailService) + { + _glDetailService = glDetailService; + } + + [HttpPost("{configurationGuid}/[action]")] + public async Task GetDepartmentData(Guid configurationGuid, GLDepartmentAccountData departmentAccountData, CancellationToken cancellationToken) + { + var result = await _glDetailService.GetOverallDetailAsync(configurationGuid, departmentAccountData, cancellationToken); + return result; + } + + [HttpPost("{configurationGuid}/[action]")] + public async Task GetStatisticData(Guid configurationGuid, GLDepartmentAccountData departmentAccountData, CancellationToken cancellationToken) + { + var result = await _glDetailService.GetStatisticDetailAsync(configurationGuid, departmentAccountData, cancellationToken); + return result; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/GeneralLedgerOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/GeneralLedgerOpportunitiesController.cs new file mode 100644 index 0000000..5e5703f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/GeneralLedgerOpportunitiesController.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class GeneralLedgerOpportunitiesController : OpportunityController + { + + public GeneralLedgerOpportunitiesController(IGLOpportunityService opportunityService) + : base(opportunityService) + { + } + + [HttpPost("")] + public async Task> GetOpportunities(GLOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Export(GLOpportunityExportOptions exportOptions, CancellationToken cancellationToken) + { + var workbook = await ((IGLOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken); + return new ExcelContentResult(workbook, "GL_Opportunities"); + + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await ((IGLOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> Create(GLOpportunitySaveModel opportunity, CancellationToken cancellationToken) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + opportunity.AuthorGuid = User.GetUserGuid(); + opportunity.AuthorName = User.Identity.Name; + + var newOpportunityId = await ((IGLOpportunityService)OpportunityService).CreateAsync(opportunity, cancellationToken); + return Ok(newOpportunityId); + } + + [HttpDelete("{id}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task Delete(Guid id, CancellationToken cancellationToken) + => await OpportunityService.DeleteAsync(id, cancellationToken); + } + + [ExcludeFromCodeCoverage] + public class GLOpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public GLFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class GLOpportunityExportOptions + { + public SortOptions SortOptions { get; set; } + public GLFilters Filters { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/GudidFileController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/GudidFileController.cs new file mode 100644 index 0000000..85e447b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/GudidFileController.cs @@ -0,0 +1,87 @@ +using Hangfire; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Strata.ContinuousImprovement.Biz.GMDN; +using Strata.ContinuousImprovement.Api.Extensions; +using Strata.ContinuousImprovement.Api.Attributes; +using Strata.ContinuousImprovement.Biz.GMDN.Jobs; +using System; +using Microsoft.Extensions.Options; +using Strata.ContinuousImprovement.Biz.Hangfire; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + + public class GudidFileController : ControllerBase + { + private readonly IGUDIDService _gudidService; + private readonly IBatchJobClient _batchJobClient; + private readonly string _datadogUrl; + private readonly string _hangfireUrl; + + + public GudidFileController( + IBatchJobClient batchJobClient, + IGUDIDService gudidService, + IOptions accessGudidAccessor, + IOptions datadogAccessor, + IOptions sectionAccessor) + { + _gudidService = gudidService; + _batchJobClient = batchJobClient; + var datadog = datadogAccessor.Value ?? throw new ArgumentNullException(nameof(datadogAccessor)); + _datadogUrl = datadog.SeedGMDNJob; + var hangfire = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(sectionAccessor)); + _hangfireUrl = hangfire.Url; + } + + [HttpPost("{dataGroup}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status415UnsupportedMediaType)] + [DisableFormValueModelBinding] + [DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = int.MaxValue, ValueLengthLimit = int.MaxValue)] + public async Task> SeedGUDID(DataGroupGudId dataGroup, [BindNever] IFormFile file, CancellationToken cancellationToken) + { + var userName = User?.Identity?.Name; + 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 (dataGroup) + { + case DataGroupGudId.Identifiers: + await _gudidService.SeedGUDID("tmp", "gudid_identifiers", fileSection.FileStream, fileSection.FileName, userName, cancellationToken); + var jobId = _batchJobClient.StartNew(batch => { batch.Enqueue(job => job.MergeGUDIDIdentifierData(cancellationToken)); }); + return $"{_hangfireUrl}/jobs/details/{jobId}"; + case DataGroupGudId.Terms: + await _gudidService.SeedGUDID("public", "gudid_terms", fileSection.FileStream, fileSection.FileName, userName, cancellationToken); + break; + case DataGroupGudId.Devices: + await _gudidService.SeedGUDID("public", "gudid_devices", fileSection.FileStream, fileSection.FileName, userName, cancellationToken); + break; + } + return Ok(); + } + + [HttpGet("[action]")] + public string DatadogUrl() + { return _datadogUrl; } + + public enum DataGroupGudId + { + Terms, + Devices, + Identifiers + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/HangfireController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/HangfireController.cs new file mode 100644 index 0000000..f1fb510 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/HangfireController.cs @@ -0,0 +1,62 @@ +using Hangfire; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Strata.ContinuousImprovement.Biz.GMDN; +using Strata.ContinuousImprovement.Biz.Hangfire; +using Strata.Hangfire.Jazz.Client; +using Strata.Hangfire.Jazz.Client.Models; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class HangfireController : ControllerBase + { + private readonly IJazzHangfireServiceClient _hangfireService; + private readonly string _accessGudidUrl; + private readonly string _datadogUrl; + + public HangfireController( + IJazzHangfireServiceClient jazzHangfireServiceClient, + IOptions accessGudidAccessor, + IOptions datadogAccessor, + IOptions sectionAccessor) + { + _hangfireService = jazzHangfireServiceClient; + var accessGudid = accessGudidAccessor.Value ?? throw new ArgumentNullException(nameof(accessGudidAccessor)); + _accessGudidUrl = accessGudid.Url; + var datadog = datadogAccessor.Value ?? throw new ArgumentNullException(nameof(datadogAccessor)); + _datadogUrl = datadog.SeedGMDNJob; + var hangfire = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(sectionAccessor)); + + } + + [HttpGet("{jobId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> JobStatus(Guid jobId, CancellationToken cancellationToken) + { + var result = await _hangfireService.GetJobInfoAsync(jobId, cancellationToken); + return Ok(result); + } + + [HttpGet("[action]")] + public string DatadogUrl() + { return _datadogUrl; } + + [HttpGet("[action]")] + public string AccessGudidUrl() + { return _accessGudidUrl; } + + public enum DataGroup + { + Terms, + Devices, + Identifiers + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/InitiativeDetailColumnsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/InitiativeDetailColumnsController.cs new file mode 100644 index 0000000..8c41436 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/InitiativeDetailColumnsController.cs @@ -0,0 +1,91 @@ +using Microsoft.AspNetCore.Mvc; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.InitiativeDetailColumns; +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class InitiativeDetailColumnsController : ControllerBase + { + private readonly IInitiativeDetailColumnService _initiativeDetailColumnService; + private readonly IInitiativeRollupColumnService _initiativeRollupColumnService; + + public InitiativeDetailColumnsController(IInitiativeDetailColumnService initiativeDetailColumnService, IInitiativeRollupColumnService initiativeRollupColumnService) + { + _initiativeDetailColumnService = initiativeDetailColumnService; + _initiativeRollupColumnService = initiativeRollupColumnService; + } + + [HttpGet("[action]")] + public async Task> GetColumnList(CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + return await ManageRollupColumns((await _initiativeDetailColumnService.GetColumnList(userGuid, cancellationToken)), cancellationToken); + } + + [HttpGet("[action]")] + public async Task> GetDefaultColumnList(CancellationToken cancellationToken) + { + return await ManageRollupColumns(_initiativeDetailColumnService.GetDefaultColumnList(), cancellationToken); + } + + private async Task> ManageRollupColumns(IEnumerable columns, CancellationToken cancellationToken) + { + var columnList = columns.ToList(); + // Add any custom rollups to default list + var rollupColumns = (await _initiativeRollupColumnService.GetAllAsync(cancellationToken)) + + .OrderBy(x => x.InitiativeRollupColumnId).ToList(); + if (rollupColumns.Any()) + { + Enumerable.Range(1, 3).ForEach(id => + { + var column = columnList.Find(dflt => rollupColumns.Any(rc => dflt.Name.StartsWith($"InitiativeRollup{id}", StringComparison.CurrentCultureIgnoreCase))); + if (column != null) + { + var rollupColumn = rollupColumns.Find(rc => rc.InitiativeRollupColumnId == id); + if (rollupColumn != null) + { + column.Heading = rollupColumn.Name; + column.InitiativeRollupColumnId = rollupColumn.InitiativeRollupColumnId; + } + else + { + columnList.Remove(column); + } + } + }); + } + else + { + // no rollups configured - remove rollup columns + columnList.RemoveAll(x => x.Name.StartsWith("InitiativeRollup")); + } + return columnList; + } + + [HttpPost("[action]")] + public async Task SetColumnList(InitiativeDetailColumnSaveInfo columnInfo, CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + await _initiativeDetailColumnService.SetColumnList(userGuid, columnInfo.Columns, cancellationToken); + } + + [ExcludeFromCodeCoverage] + public class InitiativeDetailColumnSaveInfo + { + public IEnumerable Columns { get; set; } + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/InitiativeRollupColumnsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/InitiativeRollupColumnsController.cs new file mode 100644 index 0000000..7f840fb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/InitiativeRollupColumnsController.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class InitiativeRollupColumnsController : ControllerBase + { + private readonly IInitiativeRollupColumnService _initiativeRollupColumnService; + + public InitiativeRollupColumnsController(IInitiativeRollupColumnService initiativeRollupColumnService) + { + _initiativeRollupColumnService = initiativeRollupColumnService; + } + + [HttpGet] + public async Task> GetInitiativeRollupColumnsAsync(CancellationToken cancellationToken) + { + return await _initiativeRollupColumnService.GetAllAsync(cancellationToken); + } + + [HttpGet("{initiativeRollupColumnId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetByIdAsync([FromRoute] int initiativeRollupColumnId, CancellationToken cancellationToken) + { + var rollupColumn = await _initiativeRollupColumnService.GetByIdAsync(initiativeRollupColumnId, cancellationToken); + if (rollupColumn != null) + { + return Ok(rollupColumn); + } + return NotFound(); + } + + [HttpPost("[action]/{initiativeRollupColumnId}")] + public async Task IsNameValid([FromRoute] int initiativeRollupColumnId, [FromBody] string name, CancellationToken cancellationToken) + { + return await _initiativeRollupColumnService.IsNameValid(name, cancellationToken, initiativeRollupColumnId); + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [Authorize(Policy = Policies.IsAdmin)] + public async Task> Create(InitiativeRollupColumnCreateInfo createInfo, CancellationToken cancellationToken) + { + if (await _initiativeRollupColumnService.ContainsNameAsync(createInfo.Name, cancellationToken)) + { + return BadRequest(); + } + return await _initiativeRollupColumnService.CreateAsync(createInfo, cancellationToken); + } + + [HttpPut("{initiativeRollupColumnId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + [Authorize(Policy = Policies.IsAdmin)] + public async Task> Update([FromRoute] int initiativeRollupColumnId, [FromBody] InitiativeRollupColumnSaveInfo initiativeRollupColumnSaveInfo, CancellationToken cancellationToken) + { + if (await _initiativeRollupColumnService.ContainsNameAsync(initiativeRollupColumnSaveInfo.Name, cancellationToken, initiativeRollupColumnId)) + { + return BadRequest(); + } + if (!await _initiativeRollupColumnService.ContainsAsync(initiativeRollupColumnId, cancellationToken)) + { + return NotFound(); + } + + return await _initiativeRollupColumnService.UpdateAsync(initiativeRollupColumnId, initiativeRollupColumnSaveInfo, cancellationToken); + } + + [HttpDelete("{initiativeRollupColumnId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + [Authorize(Policy = Policies.IsAdmin)] + public async Task Delete([FromRoute] int initiativeRollupColumnId, CancellationToken cancellationToken) + { + if (!await _initiativeRollupColumnService.ContainsAsync(initiativeRollupColumnId, cancellationToken)) + { + return NotFound(); + } + + await _initiativeRollupColumnService.DeleteAsync(initiativeRollupColumnId, cancellationToken); + return NoContent(); + } + + [HttpGet("{initiativeRollupColumnId}/values")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetInitiativeRollupValuesAsync([FromRoute] int initiativeRollupColumnId, CancellationToken cancellationToken) + { + var values = await _initiativeRollupColumnService.GetByRollupValuesById(initiativeRollupColumnId, cancellationToken); + if (values != null) + { + return Ok(values); + } + + return NotFound(); + } + + [HttpPut("{initiativeRollupColumnId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Authorize(Policy = Policies.IsAdmin)] + public async Task> AddRollupValueAsync([FromRoute] int initiativeRollupColumnId, [FromBody] string value, CancellationToken cancellationToken) + { + var values = await _initiativeRollupColumnService.GetByRollupValuesById(initiativeRollupColumnId, cancellationToken); + if (values == null) { return NotFound(); } + if (values.Select(v => v.Value).Contains(value)) { return BadRequest(); } + return await _initiativeRollupColumnService.AddRollupValue(initiativeRollupColumnId, value, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/InitiativesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/InitiativesController.cs new file mode 100644 index 0000000..59448d6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/InitiativesController.cs @@ -0,0 +1,643 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Extensions; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives; +using Strata.ContinuousImprovement.Biz.ChargeCode.Jobs; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Encounter.Initiatives; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives; +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Initiatives; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Initiatives.Filters; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Payroll.Initiatives; +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using Strata.Hangfire.Jazz.Client; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class InitiativesController : ControllerBase + { + private readonly IInitiativeService _initiativeService; + private readonly ICCOpportunityService _ccOpportunityService; + private readonly ICCInitiativeService _ccInitiativeService; + private readonly IENOpportunityService _enOpportunityService; + private readonly IENInitiativeService _enInitiativeService; + private readonly IFFOpportunityService _ffOpportunityService; + private readonly IFFInitiativeService _ffInitiativeService; + private readonly IGLOpportunityService _glOpportunityService; + private readonly IGLInitiativeService _glInitiativeService; + private readonly IPROpportunityService _prOpportunityService; + private readonly IPRInitiativeService _prInitiativeService; + private readonly IStakeholderUser _stakeholderUser; + private readonly IHubContext _notificationHubContext; + private readonly ILogger _logger; + private readonly ICCInitiativeJob _ccInitiativeJob; + private readonly IJazzHangfireServiceClient _hangfireService; + private readonly IInitiativeRollupColumnService _initiativeRollupColumnService; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + private readonly IAllOpportunityService _allOpportunityService; + +#pragma warning disable S107 // Constructor has NN parameters, which is greater than the 7 authorized + public InitiativesController(IInitiativeService initiativeService, + ICCOpportunityService ccOpportunityService, + ICCInitiativeService ccInitiativeService, + IENOpportunityService enOpportunityService, + IENInitiativeService enInitiativeService, + IFFOpportunityService ffOpportunityService, + IFFInitiativeService ffInitiativeService, + IGLOpportunityService glOpportunityService, + IGLInitiativeService glInitiativeService, + IPROpportunityService prOpportunityService, + IPRInitiativeService prInitiativeService, + IStakeholderUser stakeholderUser, + IHubContext notificationHubContext, + ILogger logger, + ICCInitiativeJob ccInitiativeJob, + IJazzHangfireServiceClient hangfireService, + IInitiativeRollupColumnService initiativeRollupColumnService, + IFeatureFlagServiceClient featureFlagServiceClient, + IAllOpportunityService allOpportunityService) +#pragma warning restore S107 // Constructor has NN parameters, which is greater than the 7 authorized + { + _initiativeService = initiativeService; + _ccOpportunityService = ccOpportunityService; + _ccInitiativeService = ccInitiativeService; + _enOpportunityService = enOpportunityService; + _enInitiativeService = enInitiativeService; + _ffOpportunityService = ffOpportunityService; + _ffInitiativeService = ffInitiativeService; + _glOpportunityService = glOpportunityService; + _glInitiativeService = glInitiativeService; + _prOpportunityService = prOpportunityService; + _prInitiativeService = prInitiativeService; + _stakeholderUser = stakeholderUser; + _notificationHubContext = notificationHubContext; + _logger = logger; + _ccInitiativeJob = ccInitiativeJob; + _hangfireService = hangfireService; + _initiativeRollupColumnService = initiativeRollupColumnService; + _featureFlagServiceClient = featureFlagServiceClient; + _allOpportunityService = allOpportunityService; + } + + [HttpPost()] + public async Task> GetInitiatives(InitiativeFilters filters, CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + var initiatives = await _initiativeService.GetInitiatives(filters, cancellationToken); + await _initiativeService.SetFilterSettings(userGuid, filters, cancellationToken); + return initiatives; + } + + [HttpGet("{initiativeGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetById(Guid initiativeGuid, CancellationToken cancellationToken) + { + var initiative = await _initiativeService.GetById(initiativeGuid, cancellationToken); + return initiative.MapToDetail(); + } + + [HttpGet("{initiativeGuid}/[action]")] + public async Task GetInitiativeInfo(Guid initiativeGuid, CancellationToken cancellationToken) + => await _initiativeService.GetInitiativeInfoAsync(initiativeGuid, cancellationToken); + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status202Accepted)] + public async Task Export(bool exportAll, [FromBody] InitiativeExportOptions filters, CancellationToken cancellationToken) + { + var workbook = await _initiativeService.ExportAsync(exportAll, User.GetUserGuid(), filters.SortOptions, filters.Filters, cancellationToken); + return new ExcelContentResult(workbook, "Initiatives"); + } + + [HttpGet("[action]")] + public async Task> GetWorkstreams(CancellationToken cancellationToken) + { + return await _initiativeService.GetWorkstreams(cancellationToken); + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptions(Guid configurationGuid, CancellationToken cancellationToken) + { + return await _initiativeService.GetFilterOptions(configurationGuid, cancellationToken); + } + + [HttpGet("[action]")] + public async Task GetFilterSettings(CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + return await _initiativeService.GetFilterSettings(userGuid, cancellationToken); + } + + [HttpPost("[action]")] + public async Task SetFilterSettings(InitiativeFilters filters, CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + return await _initiativeService.SetFilterSettings(userGuid, filters, cancellationToken); + } + + [HttpPut("[action]")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task SetRampupInfo(InitiativeDto initiative, CancellationToken cancellationToken) + { + try + { + var found = await _initiativeService.GetById(initiative.InitiativeGuid, cancellationToken); + if (found == null) + { + return NotFound(); + } + + await _initiativeService.SetRampupInfo(initiative, cancellationToken); + return NoContent(); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, info: new Dictionary { { nameof(initiative), initiative } }); + return BadRequest(); + } + } + + [HttpPut("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Authorize(Policy = Policies.IsAdmin)] + public async Task SetInitiativeRollupValue(InitiativeRollupValueInfo initiativeRollupValue, CancellationToken cancellationToken) + { + if (!await _initiativeService.ContainsAsync(initiativeRollupValue.InitiativeGuid, cancellationToken)) + { + return NotFound(); + } + if (!await _initiativeRollupColumnService.ContainsAsync(initiativeRollupValue.RollupId, cancellationToken)) + { + return BadRequest(); + } + await _initiativeService.SetRollupValue(initiativeRollupValue.InitiativeGuid, initiativeRollupValue.RollupId, initiativeRollupValue.RollupValueId, cancellationToken); + return Ok(); + } + + [ExcludeFromCodeCoverage] + public class InitiativeRollupValueInfo + { + public Guid InitiativeGuid { get; set; } + public int RollupId { get; set; } + public int RollupValueId { get; set; } + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task CreateChargeCode(CCInitiativeDto initiative, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(initiative.Initiative.Name); + try + { + var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + var isNameValid = await _allOpportunityService.NameIsValid(initiative.Initiative.ConfigurationGuid, (int)OpportunityTypes.ChargeCode, initiative.Initiative.Name, + initiative.Initiative.OpportunityGuid, cancellationToken); + if (dbGuid == Guid.Empty || !isNameValid) + { + return BadRequest(); + } + await hub.SendOpportunityCreate(initiative.Initiative.Name); + if (initiative.Initiative.OpportunityGuid == Guid.Empty) + { + await _ccInitiativeJob.CreateChargeCodeInitiative(initiative.Initiative, initiative.NetworkOpportunityId, dbGuid, cancellationToken); + } + else + { + var opportunity = await _ccOpportunityService.GetById(initiative.Initiative.OpportunityGuid, cancellationToken); + + // Save Initiative + var newInitiative = initiative.Initiative.MapToInitiative(_stakeholderUser); + var initiativeGuid = await _ccInitiativeService.CreateChargeCodeInitiative(dbGuid, newInitiative, opportunity.OpportunityGuid, cancellationToken); + return Ok(initiativeGuid); + } + + return Ok(); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, info: new Dictionary { { "initiativeName", initiative.Initiative.Name } }); + await hub.SendOpportunityCreateError(initiative.Initiative.Name, ex.Message); + return BadRequest(); + } + } + + [ExcludeFromCodeCoverage] + public class CCInitiativeDto + { + public ChargeCodeInitiativeDto Initiative { get; set; } + public int? NetworkOpportunityId { get; set; } + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> UpdateChargeCode(CCInitiativeDto initiativeOpportunity, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(initiativeOpportunity.Initiative.Name); + + try + { + var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + var isNameValid = await _allOpportunityService.NameIsValid(initiativeOpportunity.Initiative.ConfigurationGuid, (int)OpportunityTypes.ChargeCode, initiativeOpportunity.Initiative.Name, + initiativeOpportunity.Initiative.OpportunityGuid, cancellationToken); + if (dbGuid == Guid.Empty || !isNameValid) + { + return BadRequest(); + } + await hub.SendOpportunityUpdate(initiativeOpportunity.Initiative.Name); + await _ccInitiativeJob.UpdateChargeCodeInitiative(initiativeOpportunity.Initiative, initiativeOpportunity.NetworkOpportunityId, dbGuid, cancellationToken); + return Ok(); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, info: new Dictionary { { "initiativeName", initiativeOpportunity.Initiative.Name } }); + await hub.SendOpportunityCreateError(initiativeOpportunity.Initiative.Name, ex.Message); + return BadRequest(); + } + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> CreateEncounter(EncounterInitiativeDto initiative, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(initiative.Name); + try + { + var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + var isNameValid = await _allOpportunityService.NameIsValid(initiative.ConfigurationGuid, (int)OpportunityTypes.Encounter, initiative.Name, + Guid.Empty, cancellationToken); + if (dbGuid == Guid.Empty || !isNameValid) + { + return BadRequest(); + } + await hub.SendOpportunityCreate(initiative.Name); + var opportunityWithFilters = initiative.MapToDataModel(User); + await _enOpportunityService.SetOpportunityFilters(opportunityWithFilters, cancellationToken); + var newOpportunity = opportunityWithFilters.MapToDataModel(User); + var isTempoPage = await _featureFlagServiceClient.IsEnabledAsync("enableencounteropportunityintempo", dbGuid, cancellationToken: cancellationToken); + + var jobId = await _enOpportunityService.CreateAsync(newOpportunity, dbGuid, isTempoPage, cancellationToken); + + await hub.SendInitiativeCreate(initiative.Name); + Initiative newInitiative = initiative.MapToInitiative(_stakeholderUser); + await _enInitiativeService.CreateInitiative(dbGuid, newInitiative, newOpportunity.OpportunityGuid, cancellationToken); + return newOpportunity.OpportunityGuid; + } + catch (Exception ex) + { + _logger.LogServiceError(ex, ex.Message); + return Guid.Empty; + } + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> UpdateEncounter(EncounterInitiativeDto initiativeDto, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(initiativeDto.Name); + + try + { + var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + var isNameValid = await _allOpportunityService.NameIsValid(initiativeDto.ConfigurationGuid, (int)OpportunityTypes.Encounter, initiativeDto.Name, + initiativeDto.OpportunityGuid, cancellationToken); + if (dbGuid == Guid.Empty || !isNameValid) + { + return BadRequest(); + } + var initiative = initiativeDto.MapToInitiative(_stakeholderUser); + + if (initiativeDto.InitiativeGuid == Guid.Empty) + { + await hub.SendInitiativeCreate(initiativeDto.Name); + var newInitiativeGuid = await _enInitiativeService.CreateInitiative(dbGuid, initiative, initiativeDto.OpportunityGuid, cancellationToken); + initiativeDto.InitiativeGuid = newInitiativeGuid; + } + else + { + var updatedOpportunity = initiativeDto.MapToDataModel(User, false); + var jobId = await _enOpportunityService.UpdateAsync(dbGuid, updatedOpportunity, cancellationToken); + + await _enInitiativeService.UpdateInitiative(initiative, updatedOpportunity.OpportunityGuid, cancellationToken); + } + + return Ok(initiativeDto); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, ex.Message); + return BadRequest(); + } + } + + /// + /// Hangfire job helper to wait for job completion and check success. + /// + /// + /// + /// + private async Task JobFinishedSuccessfully(Guid jobId, CancellationToken cancellationToken) + { + // Wait for the job to finish + var result = await _hangfireService.GetJobInfoAsync(jobId, cancellationToken); + while (result.IsRunningStatus()) + { + await Task.Delay(500, cancellationToken); + result = await _hangfireService.GetJobInfoAsync(jobId, cancellationToken); + } + return result.IsSuccessful(); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> CreateFreeForm(FreeFormInitiativeDto initiative, CancellationToken cancellationToken) + { + try + { + var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + if (dbGuid == Guid.Empty) + { + return BadRequest(); + } + var newOpportunity = initiative.MapToDataModel(User); + var opportunityGuid = await _ffOpportunityService.CreateAsync(newOpportunity, cancellationToken); + + var newInitiative = initiative.MapToInitiative(_stakeholderUser); + await _ffInitiativeService.CreateInitiative(dbGuid, newInitiative, opportunityGuid, cancellationToken); + return newOpportunity.OpportunityGuid; + } + catch (Exception ex) + { + _logger.LogServiceError(ex, ex.Message); + return Guid.Empty; + } + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> UpdateFreeForm(FreeFormInitiativeDto initiativeDto, CancellationToken cancellationToken) + { + try + { + var dbGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + if (dbGuid == Guid.Empty) + { + return BadRequest(); + } + var initiative = initiativeDto.MapToInitiative(_stakeholderUser); + + if (initiativeDto.InitiativeGuid == Guid.Empty) + { + var newInitiativeGuid = await _ffInitiativeService.CreateInitiative(dbGuid, initiative, initiativeDto.OpportunityGuid, cancellationToken); + initiativeDto.InitiativeGuid = newInitiativeGuid; + } + else + { + var updatedOpportunity = initiativeDto.MapToOpportunity(); + await _ffOpportunityService.UpdateAsync(updatedOpportunity, cancellationToken); + await _ffInitiativeService.UpdateAsync(initiative, cancellationToken); + } + + return Ok(initiativeDto); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, ex.Message); + return BadRequest(default(FreeFormInitiativeDto)); + } + } + + [HttpGet("{initiativeGuid}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task Finalize(Guid initiativeGuid, CancellationToken cancellationToken) + { + try + { + await _initiativeService.FinalizeInitiative(initiativeGuid, cancellationToken); + if (!(await RunTracking(initiativeGuid, cancellationToken))) + { + throw new Exception($"Initiative tracking job did not complete successfully for initiative {initiativeGuid}."); + } + return Ok(); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + return BadRequest(); + } + } + + [HttpPost("[action]")] + [ProducesResponseType(typeof(Guid), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> CreateGeneralLedger(GeneralLedgerInitiativeDto initiative, CancellationToken cancellationToken) + { + var opportunityGuid = await CreateOpportunityAndInitiativeWithJob( + initiative.MapToDataModel(User), + initiative.MapToInitiative(_stakeholderUser), + _glOpportunityService.CreateAsync, + _glInitiativeService.CreateInitiative, + OpportunityTypes.GeneralLedger.GetDisplayName(), + cancellationToken); + + return CreatedAtAction(nameof(CreateGeneralLedger), null, opportunityGuid); + } + + [HttpPost("[action]")] + [ProducesResponseType(typeof(Guid), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> CreatePayroll(PayrollInitiativeDto initiative, CancellationToken cancellationToken) + { + var opportunityGuid = await CreateOpportunityAndInitiativeWithJob( + initiative.MapToDataModel(User), + initiative.MapToInitiative(_stakeholderUser), + _prOpportunityService.CreateAsync, + _prInitiativeService.CreateInitiative, + OpportunityTypes.Payroll.GetDisplayName(), + cancellationToken); + + return CreatedAtAction(nameof(CreatePayroll), null, opportunityGuid); + } + + private async Task> CreateOpportunityAndInitiativeWithJob( + TOpportunityDataModel newOpportunity, + Initiative initiative, + Func> createOpportunityFunc, + Func> createInitiativeFunc, + string opportunityType, + CancellationToken cancellationToken) + where TOpportunityDataModel : IOpportunity + { + ArgumentNullException.ThrowIfNull(initiative); + + var dbGuid = User.GetDatabaseGuid(); + if (dbGuid == Guid.Empty) + { + throw new Exception("Invalid database context."); + } + + // Use the opportunity GUID from the data model + var opportunityGuid = newOpportunity.OpportunityGuid; + var jobId = await createOpportunityFunc(newOpportunity, cancellationToken); + + // Result is a job ID - wait for completion + var jobFinishedSuccessfully = await JobFinishedSuccessfully(jobId, cancellationToken); + if (!jobFinishedSuccessfully) + { + throw new Exception($"{opportunityType} Opportunity creation job did not complete successfully."); + } + + var initiativeGuid = await createInitiativeFunc(dbGuid, initiative, opportunityGuid, cancellationToken); + if (initiativeGuid == Guid.Empty) + { + throw new Exception($"{opportunityType} Initiative creation failed for opportunity {opportunityGuid}."); + } + return opportunityGuid; + } + + [HttpGet("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> GetCriteria(CancellationToken cancellationToken) + { + return await _initiativeService.GetCriteria(cancellationToken); + } + + [HttpGet("[action]/{initiativeGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task GetScores(Guid initiativeGuid, CancellationToken cancellationToken) + { + return await _initiativeService.GetScores(initiativeGuid, cancellationToken); + } + + [HttpPut("set-scores")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task SetScores(InitiativeScoresDto scores, CancellationToken cancellationToken) + { + var result = await _initiativeService.SetScores(scores, cancellationToken); + return result; + } + + /// + /// Runs tracking for an initiative and waits for completion. + /// + /// + /// + /// + private async Task RunTracking(Guid initiativeGuid, CancellationToken cancellationToken) + { + var jobId = await _initiativeService.RunTracking(initiativeGuid, cancellationToken); + var jobFinishedSuccessfully = await JobFinishedSuccessfully(jobId, cancellationToken); + if (!jobFinishedSuccessfully) + { + throw new Exception($"Initiative tracking job did not complete successfully for initiative {initiativeGuid}."); + } + return jobFinishedSuccessfully; + } + + private static readonly List SupportedOpportunityTypesForNameRetrieval = + [ + (int)OpportunityTypes.GeneralLedger, + (int)OpportunityTypes.Payroll + ]; + + /// + /// Retrieves the opportunity ID for a given initiative or opportunity name. + /// + /// The name to search for. + /// Cancellation token for the operation. + /// The opportunity GUID if found. + [HttpGet("[action]")] + [ProducesResponseType(typeof(Guid), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetOpportunityGuidByName([FromQuery] Guid configurationGuid, + [FromQuery] int opportunityType, [FromQuery] string name, CancellationToken cancellationToken) + { + if (configurationGuid == Guid.Empty) { return BadRequest("Invalid configuration GUID."); } + if (!SupportedOpportunityTypesForNameRetrieval.Contains(opportunityType)) { return BadRequest("Invalid opportunity type."); } + if (string.IsNullOrWhiteSpace(name)) { return BadRequest("Name parameter is required."); } + + try + { + var initiative = await _initiativeService.FindInitiativeByPartialName(configurationGuid, name, cancellationToken); + if (initiative == null) + { + var opp = await _allOpportunityService.GetOpportunityByNamePrefix(configurationGuid, (OpportunityTypes)opportunityType, name, cancellationToken); + return opp == null + ? throw new Exception($"No Initiative/Opportunity found with name: {name}") + : (ActionResult)Ok(opp.OpportunityGuid); + } + var opportunity = await _allOpportunityService.GetByInitiativeId(initiative.InitiativeGUID, configurationGuid, (OpportunityTypes)opportunityType, cancellationToken); + return opportunity == null + ? throw new Exception($"Opportunity not found for initiative: {initiative.InitiativeGUID}") + : (ActionResult)Ok(opportunity.OpportunityGuid); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, ex.Message); + return NotFound(); + } + } + + + [ExcludeFromCodeCoverage] + public class InitiativeExportOptions + { + public SortOptions SortOptions { get; set; } + public InitiativeFilters Filters { get; set; } + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/JazzQvi/NonQviSavingsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/JazzQvi/NonQviSavingsController.cs new file mode 100644 index 0000000..23b1942 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/JazzQvi/NonQviSavingsController.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.QualityVariation.QVI; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers.JazzQvi +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/quality-variation-savings/non-qvi")] + public class NonQviSavingsController : ControllerBase + { + private readonly INonQviSavingsService _nonQviSavingsService; + + public NonQviSavingsController(INonQviSavingsService nonQviSavingsService) + { + _nonQviSavingsService = nonQviSavingsService; + } + + [HttpGet("case-type-families")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetCaseTypeFamilies( + [FromQuery] Guid configurationGuid, + [FromQuery] bool isBaseline = false, + CancellationToken cancellationToken = default) + { + if (configurationGuid == Guid.Empty) + { + return BadRequest("configurationGuid is required."); + } + + var result = await _nonQviSavingsService.GetCaseTypeFamilyInfoAsync( + configurationGuid, + isBaseline, + cancellationToken); + + return Ok(result); + } + + [HttpGet("drg-types")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetDrgTypes( + [FromQuery] Guid configurationGuid, + [FromQuery] bool isBaseline = false, + CancellationToken cancellationToken = default) + { + if (configurationGuid == Guid.Empty) + { + return BadRequest("configurationGuid is required."); + } + + var result = await _nonQviSavingsService.GetDrgTypeInfoAsync( + configurationGuid, + isBaseline, + cancellationToken); + + return Ok(result); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/JazzQvi/QviEncountersController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/JazzQvi/QviEncountersController.cs new file mode 100644 index 0000000..eb08bb8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/JazzQvi/QviEncountersController.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.QualityVariation.QVI; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers.JazzQvi +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/quality-variation-encounters")] + public class QviEncountersController : ControllerBase + { + private readonly IQviEncounterService _qviEncounterService; + + public QviEncountersController(IQviEncounterService qviEncounterService) + { + _qviEncounterService = qviEncounterService; + } + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> Get( + [FromQuery] Guid configurationGuid, + [FromQuery] DateTime startDate, + [FromQuery] DateTime endDate, + CancellationToken cancellationToken = default) + { + if (configurationGuid == Guid.Empty) + { + return BadRequest("configurationGuid is required."); + } + + var result = await _qviEncounterService.GetEncounterInfoAsync( + configurationGuid, + startDate, + endDate, + cancellationToken); + + return Ok(result); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/JazzQvi/QviLineItemSavingsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/JazzQvi/QviLineItemSavingsController.cs new file mode 100644 index 0000000..13fc435 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/JazzQvi/QviLineItemSavingsController.cs @@ -0,0 +1,91 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.QualityVariation.QVI; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers.JazzQvi +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/quality-variation-savings/line-items")] + public class QviLineItemSavingsController : ControllerBase + { + private readonly IQviLineItemSavingsService _qviLineItemSavingsService; + + public QviLineItemSavingsController(IQviLineItemSavingsService qviLineItemSavingsService) + { + _qviLineItemSavingsService = qviLineItemSavingsService; + } + + [HttpGet("case-type-families")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetCaseTypeFamilies( + [FromQuery] Guid configurationGuid, + [FromQuery] bool isBaseline = false, + CancellationToken cancellationToken = default) + { + if (configurationGuid == Guid.Empty) + { + return BadRequest("configurationGuid is required."); + } + + var result = await _qviLineItemSavingsService.GetCaseTypeFamilyInfoAsync( + configurationGuid, + isBaseline, + cancellationToken); + + return Ok(result); + } + + [HttpGet("drg-types")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetDrgTypes( + [FromQuery] Guid configurationGuid, + [FromQuery] bool isBaseline = false, + CancellationToken cancellationToken = default) + { + if (configurationGuid == Guid.Empty) + { + return BadRequest("configurationGuid is required."); + } + + var result = await _qviLineItemSavingsService.GetDrgTypeInfoAsync( + configurationGuid, + isBaseline, + cancellationToken); + + return Ok(result); + } + + [HttpGet("encounter-savings")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task>> GetEncounterSavings( + [FromQuery] Guid configurationGuid, + [FromQuery] Guid? opportunityGuid = null, + [FromQuery] bool? isBaseline = null, + CancellationToken cancellationToken = default) + { + if (configurationGuid == Guid.Empty) + { + return BadRequest("configurationGuid is required."); + } + + var result = await _qviLineItemSavingsService.GetEncounterSavingInfoAsync( + configurationGuid, + opportunityGuid, + isBaseline, + cancellationToken); + + return Ok(result); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/MappedColumnsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/MappedColumnsController.cs new file mode 100644 index 0000000..1f9b235 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/MappedColumnsController.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.MappedColumns; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class MappedColumnsController( + IMappedColumnService mappedColumnService + ) : ControllerBase + { + [HttpGet("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> GetColumnList(string name, CancellationToken cancellationToken) + { + var result = await mappedColumnService.GetColumnList(name, cancellationToken); + return Ok(result); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public async Task SetColumnList(ColumnListRequest columnListRequest, CancellationToken cancellationToken) + { + await mappedColumnService.SetColumnList(columnListRequest.Name, columnListRequest.Columns, cancellationToken); + return NoContent(); + } + + public class ColumnListRequest + { + public string Name { get; set; } + public IEnumerable Columns { get; set; } + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/NetworkOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/NetworkOpportunitiesController.cs new file mode 100644 index 0000000..1afaaf7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/NetworkOpportunitiesController.cs @@ -0,0 +1,118 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class NetworkOpportunitiesController : ControllerBase + { + private readonly INetworkOpportunityService _networkOpportunityService; + private readonly List _creators; + private readonly ILogger _logger; + + public NetworkOpportunitiesController(INetworkOpportunityService networkOpportunityService, + IOptions sectionAccessor, + ILogger logger) + { + _networkOpportunityService = networkOpportunityService; + var networkOpportunity = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(NetworkOpportunitySection)); + _creators = networkOpportunity.Creators?.Distinct().ToList() ?? new List(); + _logger = logger; + var dict = new Dictionary { { "service", nameof(NetworkOpportunitiesController) }, { "creators", _creators } }; + using (_logger.BeginScope(dict)) + _logger.LogInformation("Network Opportunity Creators"); + } + + [HttpGet("[action]")] + public async Task GetEnabled(CancellationToken cancellationToken) + { + var dict = new Dictionary { + { "service", nameof(NetworkOpportunitiesController) }, + { "creators", _creators }, + { "isSdtEmployee", User.IsSdtEmployee() } + }; + using (_logger.BeginScope(dict)) + _logger.LogInformation("Network Opportunity Enabled"); + if (User.IsSdtEmployee()) { return true; } + var databaseGuid = ClaimsPrincipalExtensions.GetDatabaseGuid(User); + return await _networkOpportunityService.GetEnabledAsync(databaseGuid, cancellationToken); + } + + [HttpGet("[action]")] + public async Task IsCreator() + { + var user = User?.Identity?.Name ?? ""; + var isCreator = _creators.Any() && (_creators.Contains(user) || _creators.Any(x => x == "*")); + var dict = new Dictionary { + { "service", nameof(NetworkOpportunitiesController) }, + { "creators", _creators }, + { "user", user }, + { "isCreator", isCreator } + }; + using (_logger.BeginScope(dict)) + _logger.LogInformation("Network Opportunity IsCreator"); + return isCreator; + } + + [HttpPost("[action]")] + public async Task> GetOpportunities(NetworkOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await _networkOpportunityService.GetOpportunitiesAsync(User, loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> Create([FromBody] NetworkOpportunitySaveModel opportunitySaveModel, CancellationToken cancellationToken) + { + return await _networkOpportunityService.CreateAsync(opportunitySaveModel, cancellationToken); + } + + [HttpPut("{networkOpportunityId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task Update(int networkOpportunityId, NetworkOpportunitySaveModel opportunitySaveModel, CancellationToken cancellationToken) + { + if (networkOpportunityId != opportunitySaveModel.NetworkOpportunityId) + { + return BadRequest(); + } + + await _networkOpportunityService.UpdateAsync(opportunitySaveModel, cancellationToken); + + return NoContent(); + } + + [HttpDelete("{networkOpportunityId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> Delete(int networkOpportunityId, CancellationToken cancellationToken) + { + return Ok(await _networkOpportunityService.DeleteAsync(networkOpportunityId, cancellationToken)); + } + } + + [ExcludeFromCodeCoverage] + public class NetworkOpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public NetworkOpportunityFilters Filters { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/OpportunityTypeWorkbooksController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/OpportunityTypeWorkbooksController.cs new file mode 100644 index 0000000..61ba170 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/OpportunityTypeWorkbooksController.cs @@ -0,0 +1,53 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class OpportunityTypeWorkbooksController : ControllerBase + { + private readonly IOpportunityTypeWorkbookService _opportunityTypeWorkbookService; + + public OpportunityTypeWorkbooksController(IOpportunityTypeWorkbookService opportunityTypeWorkbook) + { + _opportunityTypeWorkbookService = opportunityTypeWorkbook; + } + + [HttpGet("{opportunityType}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task>> GetOpportunityTypeWorkbooks([FromRoute] int opportunityType, CancellationToken cancellationToken) + { + return Ok(await _opportunityTypeWorkbookService.GetOpportunityTypeWorkbooksAsync(opportunityType, cancellationToken)); + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> CreateOpportunityTypeWorkbooks([FromBody] IEnumerable opportunityTypeWorkbooks, CancellationToken cancellationToken) + { + return Ok(await _opportunityTypeWorkbookService.CreateOpportunityTypeWorkbooksAsync(opportunityTypeWorkbooks, cancellationToken)); + } + + [HttpDelete("{opportunityType}/workbook/{workbookId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task Delete([FromRoute] int opportunityType, [FromRoute] int workbookId, CancellationToken cancellationToken) + { + if (!await _opportunityTypeWorkbookService.ContainsAsync(opportunityType, workbookId, cancellationToken)) + { + return NotFound(); + } + + await _opportunityTypeWorkbookService.DeleteAsync(opportunityType, workbookId, cancellationToken); + return NoContent(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/OpportunityWorkbooksController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/OpportunityWorkbooksController.cs new file mode 100644 index 0000000..ce752cc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/OpportunityWorkbooksController.cs @@ -0,0 +1,101 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class OpportunityWorkbooksController : ControllerBase + { + private readonly IOpportunityWorkbookService _opportunityWorkbookService; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + + public OpportunityWorkbooksController(IOpportunityWorkbookService opportunityWorkbook, IFeatureFlagServiceClient featureFlagServiceClient) + { + _opportunityWorkbookService = opportunityWorkbook; + _featureFlagServiceClient = featureFlagServiceClient; + } + + [HttpGet("{opportunityGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task>> GetOpportunityWorkbooks([FromRoute] Guid opportunityGuid, CancellationToken cancellationToken) + { + return Ok(await _opportunityWorkbookService.GetOpportunityWorkbooksAsync(opportunityGuid, cancellationToken)); + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> CreateOpportunityWorkbooks([FromBody] IEnumerable opportunityWorkbooks, CancellationToken cancellationToken) + { + return Ok(await _opportunityWorkbookService.CreateOpportunityWorkbooksAsync(opportunityWorkbooks, cancellationToken)); + } + + [HttpDelete("{opportunityGuid}/workbook/{workbookId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task Delete([FromRoute] Guid opportunityGuid, [FromRoute] int workbookId, CancellationToken cancellationToken) + { + if (!await _opportunityWorkbookService.ContainsAsync(opportunityGuid, workbookId, cancellationToken)) + { + return NotFound(); + } + + await _opportunityWorkbookService.DeleteAsync(opportunityGuid, workbookId, cancellationToken); + return NoContent(); + } + + + [HttpGet("strategic/{opportunityId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task>> GetStrategicOpportunityWorkbooks([FromRoute] long opportunityId, CancellationToken cancellationToken) + { + var workbooks = await _opportunityWorkbookService.GetStrategicOpportunityWorkbooksAsync(opportunityId, cancellationToken); + if (workbooks == null) + { + return NotFound(); + } + return Ok(workbooks); + } + + [HttpPost("strategic")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task>> CreateStrategicOpportunityWorkbooks([FromBody] IEnumerable opportunityWorkbooks, CancellationToken cancellationToken) + { + return Ok(await _opportunityWorkbookService.CreateStrategicOpportunityWorkbooksAsync(opportunityWorkbooks, cancellationToken)); + } + + [HttpDelete("strategic/{opportunityId}/workbook/{workbookId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task Delete([FromRoute] long opportunityId, [FromRoute] int workbookId, CancellationToken cancellationToken) + { + if (!await _opportunityWorkbookService.ContainsStrategicAsync(opportunityId, workbookId, cancellationToken)) + { + return NotFound(); + } + + await _opportunityWorkbookService.DeleteStrategicOpportunityAsync(opportunityId, workbookId, cancellationToken); + return NoContent(); + } + + [HttpGet("[action]")] + public async Task OpportunityWorkbooksEnabled(CancellationToken cancellationToken) + => await _featureFlagServiceClient.IsEnabledAsync("ciopportunityworkbooks", User?.GetDatabaseGuid() ?? Guid.Empty, cancellationToken: cancellationToken); + + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/PayrollOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/PayrollOpportunitiesController.cs new file mode 100644 index 0000000..07524b2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/PayrollOpportunitiesController.cs @@ -0,0 +1,118 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities; +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities.Filters; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class PayrollOpportunitiesController : OpportunityController + { + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + + public PayrollOpportunitiesController(IPROpportunityService opportunityService, IFeatureFlagServiceClient featureFlagServiceClient) + : base(opportunityService) + { + _featureFlagServiceClient = featureFlagServiceClient; + } + + [HttpPost("")] + public async Task> GetOpportunities(PROpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Export(PROpportunityExportOptions exportOptions, CancellationToken cancellationToken) + { + var workbook = await ((IPROpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken); + return new ExcelContentResult(workbook, "PR_Opportunities"); + + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await ((IPROpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> Create(PROpportunitySaveModel opportunity, CancellationToken cancellationToken) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + opportunity.AuthorGuid = User.GetUserGuid(); + opportunity.AuthorName = User.Identity.Name; + + var newOpportunityId = await ((IPROpportunityService)OpportunityService).CreateAsync(opportunity, cancellationToken); + return Ok(newOpportunityId); + } + + [HttpDelete("{id}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task Delete(Guid id, CancellationToken cancellationToken) + => await OpportunityService.DeleteAsync(id, cancellationToken); + + [HttpGet("{opportunityGuid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task> GetById(Guid opportunityGuid, CancellationToken cancellationToken) + { + var opportunity = await ((IPROpportunityService)OpportunityService).GetPROpportunityById(opportunityGuid, cancellationToken); + if (opportunity == null) + { + return NotFound(); + } + return Ok(opportunity.Mapped()); + } + + [HttpGet("{opportunityGuid}/[action]")] + public async Task> GetInitiativeInfo(Guid opportunityGuid, CancellationToken cancellationToken) + { + return await ((IPROpportunityService)OpportunityService).GetInitiativeInfoAsync(opportunityGuid, cancellationToken); + } + + [HttpGet("[action]")] + public async Task TempoPageEnabled(CancellationToken cancellationToken) + { + return await _featureFlagServiceClient.IsEnabledAsync("enablepayrollopportunityintempo", User?.GetDatabaseGuid() ?? Guid.Empty, cancellationToken: cancellationToken); + } + } + + [ExcludeFromCodeCoverage] + public class PROpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public PRFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class PROpportunityExportOptions + { + public SortOptions SortOptions { get; set; } + public PRFilters Filters { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/PeerGroupController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/PeerGroupController.cs new file mode 100644 index 0000000..8202d5f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/PeerGroupController.cs @@ -0,0 +1,115 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.PeerGroups; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class PeerGroupController : ControllerBase + { + private readonly IPeerGroupService _peerGroupService; + + public PeerGroupController(IPeerGroupService peerGroupService) + { + _peerGroupService = peerGroupService; + } + + [HttpGet] + public async Task> GetPeerGroupsAsync(CancellationToken cancellationToken) + { + return await _peerGroupService.GetAllAsync(cancellationToken); + } + + [HttpGet("{peerGroupId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetByIdAsync([FromRoute] int peerGroupId, CancellationToken cancellationToken) + { + var peerGroup = await _peerGroupService.GetByIdAsync(peerGroupId, cancellationToken); + if (peerGroup != null) + { + return Ok(peerGroup); + } + + return NotFound(); + } + + [HttpGet("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public ActionResult GetDropdownItems() + { + var dropdownItems = _peerGroupService.GetDropdownItems(); + if (dropdownItems != null) + { + return Ok(dropdownItems); + } + + return NotFound(); + } + + [HttpPost("[action]/{peerGroupId}")] + public async Task IsNameValid([FromRoute] int peerGroupId, [FromBody] string name, CancellationToken cancellationToken) + { + return await _peerGroupService.IsNameValid(name, cancellationToken, peerGroupId); + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [Authorize(Policy = Policies.IsAdmin)] + public async Task> Create(PeerGroupDto createInfo, CancellationToken cancellationToken) + { + if (!await _peerGroupService.IsNameValid(createInfo.Name, cancellationToken)) + { + return BadRequest(); + } + return await _peerGroupService.CreateAsync(createInfo, cancellationToken); + } + + [HttpPut("{peerGroupId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + [Authorize(Policy = Policies.IsAdmin)] + public async Task> Update([FromRoute] int peerGroupId, [FromBody] PeerGroupDto dto, CancellationToken cancellationToken) + { + if (!await _peerGroupService.IsNameValid(dto.Name, cancellationToken, peerGroupId)) + { + return BadRequest(); + } + if (!await _peerGroupService.ContainsAsync(peerGroupId, cancellationToken)) + { + return NotFound(); + } + + return await _peerGroupService.UpdateAsync(peerGroupId, dto, cancellationToken); + } + + [HttpDelete("{peerGroupId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + [Authorize(Policy = Policies.IsAdmin)] + public async Task Delete([FromRoute] int peerGroupId, CancellationToken cancellationToken) + { + if (!await _peerGroupService.ContainsAsync(peerGroupId, cancellationToken)) + { + return NotFound(); + } + + await _peerGroupService.DeleteAsync(peerGroupId, cancellationToken); + return NoContent(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/PickerItemsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/PickerItemsController.cs new file mode 100644 index 0000000..01ba091 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/PickerItemsController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.PickerItems; +using Strata.ContinuousImprovement.Biz.Population; +using Strata.ContinuousImprovement.Biz.Shared; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class PickerItemsController : ControllerBase + { + private readonly IPickerItemService _pickerItemService; + + public PickerItemsController(IPickerItemService pickerItemService) + { + _pickerItemService = pickerItemService; + } + + [HttpGet("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task>> GetExplorations(CancellationToken cancellationToken) + { + var items = await _pickerItemService.GetItems((item) => new PickerItem() { Text = item.Name, Value = item.ExplorationPopulationId }, cancellationToken); + return Ok(items); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/QualityVariationItemBreakdownsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/QualityVariationItemBreakdownsController.cs new file mode 100644 index 0000000..a824e70 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/QualityVariationItemBreakdownsController.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns; +using Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns.Filters; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class QualityVariationItemBreakdownsController : ControllerBase + { + private readonly IQVIBreakdownService _qviBreakdownService; + + public QualityVariationItemBreakdownsController(IQVIBreakdownService qviBreakdownService) + { + _qviBreakdownService = qviBreakdownService; + } + + [HttpPost("")] + public async Task> GetBreakdowns(QVIBreakdownsLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await _qviBreakdownService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await _qviBreakdownService.GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + } + + [ExcludeFromCodeCoverage] + public class QVIBreakdownsLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public QVIBreakdownFilters Filters { get; set; } + } +} + diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/QualityVariationItemEventsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/QualityVariationItemEventsController.cs new file mode 100644 index 0000000..b16eb23 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/QualityVariationItemEventsController.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.QualityVariation.Events; +using Strata.ContinuousImprovement.Biz.QualityVariation.Events.Filters; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class QualityVariationItemEventsController : ControllerBase + { + private readonly IQVIEventService _qviEventService; + + public QualityVariationItemEventsController(IQVIEventService qviEventService) + { + _qviEventService = qviEventService; + } + + [HttpPost("")] + public async Task> GetEvents(QVIEventsLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await _qviEventService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await _qviEventService.GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + } + + [ExcludeFromCodeCoverage] + public class QVIEventsLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public QVIEventFilters Filters { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/QualityVariationItemOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/QualityVariationItemOpportunitiesController.cs new file mode 100644 index 0000000..3d6122e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/QualityVariationItemOpportunitiesController.cs @@ -0,0 +1,83 @@ +using Hangfire; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.QualityVariation.Jobs; +using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities; +using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities.Filters; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class QualityVariationItemOpportunitiesController : OpportunityController + { + private readonly IBackgroundJobClient _backgroundJobClient; + + public QualityVariationItemOpportunitiesController( + IQVIOpportunityService opportunityService, + IBackgroundJobClient backgroundJobClient) + : base(opportunityService) + { + _backgroundJobClient = backgroundJobClient; + } + + [HttpPost("")] + public async Task> GetOpportunities(QVIOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Export(QVIOpportunityExportOptions exportOptions, CancellationToken cancellationToken) + { + var workbook = await ((IQVIOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken); + return new ExcelContentResult(workbook, "QVI_Opportunities"); + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await ((IQVIOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public IActionResult PopulateQviMetrics(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken) + { + if (configurationGuid == Guid.Empty) + { + return BadRequest("Invalid request. ConfigurationGuid is required."); + } + + var jobId = _backgroundJobClient.Enqueue(job => + job.PopulateQviMetricsAsync(configurationGuid, isBaseline, cancellationToken)); + + return Ok(jobId); + } + } + + [ExcludeFromCodeCoverage] + public class QVIOpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public QVIFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class QVIOpportunityExportOptions + { + public SortOptions SortOptions { get; set; } + public QVIFilters Filters { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/ReportController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/ReportController.cs new file mode 100644 index 0000000..756748c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/ReportController.cs @@ -0,0 +1,99 @@ +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.DbContexts.Reports; +using Strata.ContinuousImprovement.Biz.Reports; +using Strata.CoreLib.Claims.Extensions; +using Strata.Stratasphere.Biz.DbContexts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class ReportController : Controller + { + private readonly IReportService _reportService; + + public ReportController(IReportService reportService) + { + _reportService = reportService; + } + + [HttpGet("{sourceOfSavings}/[action]")] + public async Task> GetByModule(int sourceOfSavings, CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + return await _reportService.GetReportsByModule(sourceOfSavings, userGuid, User.IsSdtEmployee(), cancellationToken); + } + + [HttpGet("{opportunityGuid:guid}/[action]")] + public async Task> GetByOpportunity(Guid opportunityGuid, CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + return await _reportService.GetReportsByOpportunity(opportunityGuid, userGuid, User.IsSdtEmployee(), cancellationToken); + } + + [HttpPost("[action]")] + public async Task> GetReports(ReportRequest request, CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + var moduleRpts = await _reportService.GetReportsByModule(request.SourceOfSavings, userGuid, User.IsSdtEmployee(), cancellationToken); + var opportunityRpts = await _reportService.GetReportsByOpportunity(request.OpportunityGuid, userGuid, User.IsSdtEmployee(), cancellationToken); + return moduleRpts.Union(opportunityRpts); + } + + [HttpGet("[action]")] + public async Task> GetReports(CancellationToken cancellationToken) + { + var userGuid = User?.GetUserGuid() ?? Guid.Empty; + return await _reportService.GetReports(userGuid, User.IsSdtEmployee(), cancellationToken); + } + + [HttpPost("[action]")] + public async Task AddByOpportunity(ReportByOpportunity reportRequest, CancellationToken cancellationToken) + => await _reportService.AddReportByOpportunity(reportRequest.OpportunityGuid, reportRequest.ReportGuid, cancellationToken); + + [HttpPost("[action]")] + public async Task AddByModule(ReportByModule reportRequest, CancellationToken cancellationToken) + => await _reportService.AddReportByModule(reportRequest.SourceOfSavings, reportRequest.ReportGuid, cancellationToken); + + [HttpDelete("[action]")] + public async Task RemoveByOpportunity(ReportByOpportunity reportRequest, CancellationToken cancellationToken) + => await _reportService.DeleteReportByOpportunity(reportRequest.OpportunityGuid, reportRequest.ReportGuid, cancellationToken); + + [HttpDelete("[action]")] + public async Task RemoveByModule(ReportByModule reportRequest, CancellationToken cancellationToken) + => await _reportService.DeleteReportByModule(reportRequest.SourceOfSavings, reportRequest.ReportGuid, cancellationToken); + + [HttpGet("[action]")] + public async Task GetFolders(CancellationToken cancellationToken) + => await _reportService.GetReportFolders(cancellationToken); + + [HttpGet("{folderGuid:guid}[action]")] + public async Task> GetFolderReports(Guid folderGuid, CancellationToken cancellationToken) + => await _reportService.GetFolderReports(folderGuid, cancellationToken); + + public class ReportRequest + { + public Guid OpportunityGuid { get; set; } + public int SourceOfSavings { get; set; } + } + + public class ReportByOpportunity + { + public Guid ReportGuid { get; set; } + public Guid OpportunityGuid { get; set; } + } + + public class ReportByModule + { + public Guid ReportGuid { get; set; } + public int SourceOfSavings { get; set; } + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/SecurityController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/SecurityController.cs new file mode 100644 index 0000000..14b1b48 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/SecurityController.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.Security; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class SecurityController : ControllerBase + { + private readonly ISimpleSecurityService _securityService; + private readonly IAuthorizationServiceClient _authorizationServiceClient; + private readonly Guid CIGeneralAccessLootId = Guid.Parse("33F10831-E42E-4D48-A6FA-A110798B4F63"); + + public SecurityController(ISimpleSecurityService securityService, + IAuthorizationServiceClient authorizationServiceClient) + { + _securityService = securityService; + _authorizationServiceClient = authorizationServiceClient; + } + + [HttpGet("[action]")] + public async Task GetGeneralAccessPermission(CancellationToken cancellationToken) + => (await _authorizationServiceClient.GetPermissionAsync(CIGeneralAccessLootId, cancellationToken)).CanRead; + + [HttpGet("{lootId}")] + public async Task GetUserPermissions(Guid lootId, CancellationToken cancellationToken) + => await _authorizationServiceClient.GetPermissionAsync(lootId, cancellationToken); + + [HttpGet("[action]")] + public async Task> GetIdentities(CancellationToken cancellationToken) + => await _securityService.GetAllIdentities(cancellationToken); + + [HttpGet("{lootId}/[action]")] + public async Task> GetSimpleSecurities(Guid lootId, CancellationToken cancellationToken) + => await _authorizationServiceClient.GetUserPermissionsAsync(lootId, cancellationToken); + + [HttpPut("{lootId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> SetSimpleSecurities(Guid lootId, [FromBody] IEnumerable permissionSettings, CancellationToken cancellationToken) + { + if (permissionSettings.Any(ps => !ps.LootId.HasValue || ps.LootId.Value != lootId)) { return BadRequest(); } + var isSecurable = await _authorizationServiceClient.IsSecurable(lootId, cancellationToken); + + var userGuid = User?.GetUserGuid() ?? Guid.NewGuid(); + if (!User.IsSdtEmployee() && !isSecurable) return Ok(false); + await _authorizationServiceClient.ReplaceUserPermissionsAsync(lootId, permissionSettings, cancellationToken); + return Ok(true); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/SettingsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/SettingsController.cs new file mode 100644 index 0000000..cb5e68e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/SettingsController.cs @@ -0,0 +1,158 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Strata.ContinuousImprovement.Biz.ChargeCode; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.PerformanceEngine; +using Strata.ContinuousImprovement.Biz.Settings; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims.Extensions; +using Strata.DataSchema.Client; +using Strata.FeatureFlags.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class SettingsController : Controller + { + private readonly ISettingsService _settingsService; + private ChargeCodeOpportunityValidationSection _validationSection; + private readonly IDataSchemaService _dataSchemaService; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + + public SettingsController(ISettingsService settingsService, + IOptions sectionAccessor, + IDataSchemaService dataSchemaService, + IFeatureFlagServiceClient featureFlagServiceClient) + { + _settingsService = settingsService; + _validationSection = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(ChargeCodeOpportunityValidationSection)); + _dataSchemaService = dataSchemaService; + _featureFlagServiceClient = featureFlagServiceClient; + } + + [HttpGet("[action]")] + public async Task> GetFeatureFlagsAsync() + { + var databaseGuid = User?.GetDatabaseGuid() ?? Guid.Empty; + var featureFlags = await _featureFlagServiceClient.GetFeatureFlagClientStatesByArea(databaseGuid, "CI", CancellationToken.None); + return featureFlags.ToDictionary(key => key.Key.Replace("-", ""), val => val.Enabled); + } + + [HttpPost("[action]")] + public async Task> GetSystemSettings([FromBody] List settings, CancellationToken cancellationToken) + { + var systemSettings = await _settingsService.GetSystemSettings(settings, cancellationToken); + return systemSettings; + } + + [HttpGet("[action]")] + public int GetOpportunityLevelLimit() + { + return _validationSection.MaxOpportunityLevelSelections; + } + + [HttpGet("[action]")] + public int GetChargeCodeLimit() + { + return _validationSection.MaxChargeCodeSelections; + } + + [HttpGet("[action]")] + public PerformanceEngingeCalculationData GetPerformanceEngineCalculations() + { + // this is stubbed with test data taken from the Performance Engine tests to populate the calculations page + // we will need to modify this to take inputs from the wizards and return a PerformanceTrackingItem + + var currentYear = DateTime.Now.Year; + + double[] goalUnits = { 13221, 17628, 22035, 22035, 8814, 26442, 8814, 13221, 22035, 17628, 30849, 44070 }; + double[] baselineUnits = { 12231, 16788, 21605, 21305, 8430, 24648, 8172, 11847, 24535, 16704, 26159, 45000 }; + double[] baselineStats = { 3, 4, 5, 5, 2, 6, 2, 3, 5, 4, 7, 10 }; + double[] trackingUnits = { 54314, 8798, 31680, 21415, 25895, 0, 0, 0, 0, 0, 0, 0 }; + double[] trackingStats = { 13, 2, 8, 5, 5, 0, 0, 0, 0, 0, 0, 0 }; + + MonthlyRampUp[] rampUps = + { + new MonthlyRampUp(1, currentYear, 100), new MonthlyRampUp(2, currentYear, 100), new MonthlyRampUp(3, currentYear, 100), new MonthlyRampUp(4, currentYear, 100), + new MonthlyRampUp(5, currentYear, 100),new MonthlyRampUp(6, currentYear, 100),new MonthlyRampUp(7, currentYear, 100),new MonthlyRampUp(8, currentYear, 100), + new MonthlyRampUp(9, currentYear, 100),new MonthlyRampUp(10, currentYear, 100),new MonthlyRampUp(11, currentYear, 100),new MonthlyRampUp(12, currentYear, 100) + }; + + var trackingYearMonths = Enumerable.Range(1, 12).ToList() + .ConvertAll(i => + { + var ym = new TrackingYearMonth(currentYear, currentYear, + new FiscalMonth(Guid.NewGuid(), (byte)i, (byte)i, $"{i}", "", "", 0, 0, $"Month{i:00}", 0, 0), true, true, true); + return ym; + }).ToList(); + + var performanceBaselineData = PerformanceComparisonData.SetupBaselineDataList(baselineUnits, baselineStats, currentYear - 1); + var performanceTrackingData = PerformanceComparisonData.SetupTrackingDataList(goalUnits, trackingUnits, trackingStats, currentYear); + var group = new PerformanceGroup(1, "testCalculations"); + var options = new PerformanceOptions(false, true, true); + + var performanceTrackingItem = new PerformanceTrackingItem(group, rampUps, options, performanceBaselineData, performanceTrackingData, trackingYearMonths); + var data = new PerformanceEngingeCalculationData(performanceTrackingItem); + return data; + } + } + + public class PerformanceEngingeCalculationData + { + // inputs + public string entity { get; set; } + public string caseTypeEntity { get; set; } + public int serviceLineID { get; set; } + public int physicianGroupingAffiliationId { get; set; } + public string primaryPhysician { get; set; } + public string chargeCodeRollup { get; set; } + public string costDriver { get; set; } + public string baselineStartDate { get; set; } + public string baselineEndDate { get; set; } + + // numbers fed into calcs + public double totalVolume { get; set; } + public double totalGrossCharges { get; set; } + public double totalVariableDirectCost { get; set; } + public double totalVariableCost { get; set; } + + // calculated results + public double goalUnitVariance { get; set; } + public double actualUnitVariance { get; set; } + public double committedUnits { get; set; } + public double committedStats { get; set; } + public double baselineAverage { get; set; } + + public PerformanceEngingeCalculationData(PerformanceTrackingItem item) + { + entity = "n/a"; + caseTypeEntity = "n/a"; + serviceLineID = -1; + physicianGroupingAffiliationId = -1; + primaryPhysician = "n/a"; + chargeCodeRollup = "n/a"; + costDriver = "n/a"; + baselineStartDate = DateTime.Now.ToShortDateString(); + baselineEndDate = DateTime.Now.ToShortDateString(); + + totalVolume = -1; + totalGrossCharges = -1; + totalVariableDirectCost = -1; + totalVariableCost = -1; + + goalUnitVariance = item.MonthlyTrackingData.Sum(month => month.GoalUnitVariance); + actualUnitVariance = item.MonthlyTrackingData.Sum(month => month.ActualUnitVariance); + committedUnits = item.MonthlyTrackingData.Sum(month => month.CommittedUnits); + committedStats = item.MonthlyTrackingData.Sum(month => month.CommittedStats); + baselineAverage = item.MonthlyTrackingData.Average(month => month.BaselineAverage); + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/StrategicChargeCodeController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/StrategicChargeCodeController.cs new file mode 100644 index 0000000..8654b4c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/StrategicChargeCodeController.cs @@ -0,0 +1,132 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class StrategicChargeCodeController : ControllerBase + { + private readonly IStrategicOpportunityService _strategicOpportunityService; + private readonly IStrategicChargeCodeOpportunityDetailService _strategicChargeCodeOpportunityDetailService; + + public StrategicChargeCodeController(IStrategicOpportunityService strategicOpportunityService, IStrategicChargeCodeOpportunityDetailService strategicChargeCodeOpportunityDetailService) + { + _strategicOpportunityService = strategicOpportunityService; + _strategicChargeCodeOpportunityDetailService = strategicChargeCodeOpportunityDetailService; + } + + [HttpGet("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetOpportunityChargeCode(long opportunityId, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.GetStrategicOpportunityChargeCode(opportunityId, cancellationToken); + return result != null ? Ok(result) : NotFound(); + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CreateOpportunityChargeCode([FromBody] StrategicOpportunityChargeCodeDto opportunity, CancellationToken cancellationToken) + { + var opportunityId = await _strategicOpportunityService.CreateOpportunityChargeCodeAsync(opportunity, cancellationToken); + if (opportunityId == 0) return BadRequest(); + return Ok(opportunityId); + } + + [HttpPut("{opportunityId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> UpdateOpportunityChargeCode(long opportunityId, [FromBody] StrategicOpportunityChargeCodeUpdateDto opportunity, CancellationToken cancellationToken) + { + var strategicOpportunityId = await _strategicOpportunityService.UpdateOpportunityChargeCode(opportunityId, opportunity, cancellationToken); + return strategicOpportunityId != 0 ? Ok(true) : NotFound(); + } + + [HttpDelete("{opportunityId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task DeleteOpportunityChargeCode(long opportunityId, CancellationToken cancellationToken) + { + if (!await _strategicOpportunityService.ContainsAsync(opportunityId, cancellationToken)) + return NotFound(); + + var isDeleted = await _strategicOpportunityService.DeleteChargeCodeOpportunityAsync(opportunityId, cancellationToken); + return isDeleted ? NoContent() : BadRequest(); + } + + [HttpPost("{opportunityId}/goal-detail")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetGoalDetail(long opportunityId, [FromBody] StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken) + { + var result = await _strategicChargeCodeOpportunityDetailService.GetStrategicOpportunityDetailData(opportunityId, false, getData, cancellationToken); + return result != null ? Ok(result) : NotFound(); + } + + [HttpPost("{opportunityId}/export")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status202Accepted)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task ExportInitiative(long opportunityId, [FromBody] StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken) + { + var workbook = await _strategicChargeCodeOpportunityDetailService.ExportInitiativeAsync(opportunityId, true, getData, cancellationToken); + if (workbook == null) + return NotFound(); + return new ExcelContentResult(workbook, $"Strategic Initiative Charge Code {opportunityId} Export"); + } + + [HttpPost("{opportunityId}/export-opportunity")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task ExportOpportunity(long opportunityId, [FromBody] StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken) + { + var workbook = await _strategicChargeCodeOpportunityDetailService.ExportOpportunityAsync(opportunityId, getData, cancellationToken); + if (workbook == null) + return NotFound(); + return new ExcelContentResult(workbook, $"Strategic Opportunity {opportunityId} Export"); + } + + [HttpPost("{opportunityId}/tracking-detail")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetTrackingDetail(long opportunityId, [FromBody] StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken) + { + var result = await _strategicChargeCodeOpportunityDetailService.GetStrategicOpportunityDetailData(opportunityId, true, getData, cancellationToken); + return result != null ? Ok(result) : NotFound(); + } + + [HttpPut("{opportunityId}/details")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task UpdateDetails(long opportunityId, [FromBody] UpdateDetailsDto dto, CancellationToken cancellationToken) + { + if (dto == null || dto.Adjustments?.Any() != true || dto.Rows?.Any() != true) + return BadRequest(); + + await _strategicChargeCodeOpportunityDetailService.UpdateStrategicOpportunityDetailsData(opportunityId, dto.Adjustments, dto.Rows, cancellationToken); + return Ok(); + } + + public class UpdateDetailsDto + { + public IEnumerable Adjustments { get; set; } + public IEnumerable Rows { get; set; } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/StrategicGeneralLedgerController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/StrategicGeneralLedgerController.cs new file mode 100644 index 0000000..9f97b66 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/StrategicGeneralLedgerController.cs @@ -0,0 +1,102 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class StrategicGeneralLedgerController : ControllerBase + { + private readonly IStrategicOpportunityService _strategicOpportunityService; + private readonly IStrategicGLOpportunityDetailService _strategicGLOpportunityDetailService; + + public StrategicGeneralLedgerController(IStrategicOpportunityService strategicOpportunityService, IStrategicGLOpportunityDetailService strategicGLOpportunityDetailService) + { + _strategicOpportunityService = strategicOpportunityService; + _strategicGLOpportunityDetailService = strategicGLOpportunityDetailService; + } + + [HttpGet("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetOpportunityGL(long opportunityId, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.GetStrategicOpportunityGL(opportunityId, cancellationToken); + return result != null ? Ok(result) : NotFound(); + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> CreateOpportunityGeneralLedger([FromBody] StrategicOpportunityGLDto opportunity, CancellationToken cancellationToken) + { + var opportunityId = await _strategicOpportunityService.CreateOpportunityGeneralLedgerAsync(opportunity, cancellationToken); + if (opportunityId == 0) return BadRequest(); + return Ok(opportunityId); + } + + [HttpPut("{opportunityId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> UpdateOpportunityGeneralLedger(long opportunityId, [FromBody] StrategicOpportunityGLUpdateDto opportunity, CancellationToken cancellationToken) + { + var strategicOpportunityId = await _strategicOpportunityService.UpdateOpportunityGeneralLedger(opportunityId, opportunity, cancellationToken); + return strategicOpportunityId != 0 ? Ok(true) : NotFound(); + } + + [HttpDelete("{opportunityId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task DeleteOpportunityGeneralLedger(long opportunityId, CancellationToken cancellationToken) + { + if (!await _strategicOpportunityService.ContainsGLAsync(opportunityId, cancellationToken)) + return NotFound(); + + var isDeleted = await _strategicOpportunityService.DeleteGeneralLedgerOpportunityAsync(opportunityId, cancellationToken); + return isDeleted ? NoContent() : BadRequest(); + } + + [HttpPost("{opportunityId}/detail")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetGLDetail(long opportunityId, [FromBody] string[] dimensions, CancellationToken cancellationToken) + { + var result = await _strategicGLOpportunityDetailService.GetStrategicOpportunityGLDetailData(opportunityId, false, dimensions, cancellationToken); + return result != null ? Ok(result) : NotFound(); + } + + [HttpPost("{opportunityId}/tracking-detail")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetGLTrackingDetail(long opportunityId, [FromBody] string[] dimensions, CancellationToken cancellationToken) + { + var result = await _strategicGLOpportunityDetailService.GetStrategicOpportunityGLDetailData(opportunityId, true, dimensions, cancellationToken); + return result != null ? Ok(result) : NotFound(); + } + + [HttpPut("{opportunityId}/details")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public async Task UpdateGLDetails(long opportunityId, [FromBody] UpdateGLDetailsDto dto, CancellationToken cancellationToken) + { + if (dto == null || dto.Adjustments?.Any() != true || dto.Rows?.Any() != true) + return BadRequest(); + + await _strategicGLOpportunityDetailService.UpdateStrategicOpportunityGLDetailsData(opportunityId, dto.Adjustments, dto.Rows, cancellationToken); + return Ok(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/StrategicOpportunityController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/StrategicOpportunityController.cs new file mode 100644 index 0000000..58caf68 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/StrategicOpportunityController.cs @@ -0,0 +1,190 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class StrategicOpportunityController : ControllerBase + { + private readonly IStrategicOpportunityService _strategicOpportunityService; + + public StrategicOpportunityController(IStrategicOpportunityService strategicOpportunityService) + { + _strategicOpportunityService = strategicOpportunityService; + } + + [HttpGet("")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> GetOpportunities(CancellationToken cancellationToken) + { + return await _strategicOpportunityService.GetStrategicOpportunitiesAsync(cancellationToken); + } + + [HttpGet("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetGeneralInfo(long opportunityId, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.GetStrategicOpportunityGeneralInfo(opportunityId, cancellationToken); + return result != null ? Ok(result) : NotFound(); + } + + [HttpPost("[action]")] + public async Task IsNameValid([FromBody] string name, CancellationToken cancellationToken) + { + return await _strategicOpportunityService.NameIsValidAsync(name, cancellationToken); + } + + [HttpGet("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> GetLayout([FromRoute] long opportunityId, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.GetStrategicOpportunityLayout(opportunityId, cancellationToken); + return result; + } + + [HttpPut("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> UpdateLayout(long opportunityId, [FromBody] StrategicOpportunityLayout layout, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.UpdateStrategicOpportunityLayout(opportunityId, layout, cancellationToken); + return result ? Ok(result) : NotFound(); + } + + [HttpPut("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> SetStatus(long opportunityId, [FromBody] StrategicOpportunityUpdateStatusDto updateStatusDto, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.UpdateStrategicOpportunityStatus(opportunityId, updateStatusDto, cancellationToken); + return result ? Ok(result) : NotFound(); + } + + [HttpGet("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> GetInitiatives(CancellationToken cancellationToken) + { + return await _strategicOpportunityService.GetStrategicInitiativesAsync(cancellationToken); + } + + [HttpPut("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> UpdateInitiative(long opportunityId, [FromBody] StrategicInitiativeUpdateDto updateInitiativeDto, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.UpdateStrategicInitiative(opportunityId, updateInitiativeDto, cancellationToken); + return result ? Ok(result) : NotFound(); + } + + [HttpPut("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> UpdateRoles(long opportunityId, [FromBody] StrategicRolesUpdate updateRolesDto, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.UpdateRoles(opportunityId, updateRolesDto, cancellationToken); + return result ? Ok(result) : NotFound(); + } + + [HttpPut("{opportunityId}/ramp-ups")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> UpdateRampUps(long opportunityId, [FromBody] List rampUps, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.UpdateStrategicOpportunityRampUpsAsync(opportunityId, rampUps, cancellationToken); + return result ? Ok(result) : NotFound(); + } + + [HttpGet("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task>> GetRampUps(long opportunityId, CancellationToken cancellationToken) + { + var rampUps = await _strategicOpportunityService.GetStrategicOpportunityRampUpsAsync(opportunityId, cancellationToken); + return Ok(rampUps); + } + + [HttpGet("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> RunTrackingTask(long opportunityId, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.RunTrackingTask(opportunityId, cancellationToken); + return result ? Ok(result) : NotFound(); + } + + [HttpPut("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> CompleteInitiative(long opportunityId, [FromBody] StrategicOpportunityCompleteInitiativeData completeInitiativeData, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.CompleteInitiative(opportunityId, completeInitiativeData, cancellationToken); + return result ? Ok(result) : NotFound(); + } + + [HttpGet("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task>> GetMeasures(long opportunityId, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.GetStrategicOpportunityMeasures(opportunityId, cancellationToken); + return result.Any() ? Ok(result) : NotFound(); + } + + [HttpGet("{opportunityId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> GetPrimaryMeasure(long opportunityId, CancellationToken cancellationToken) + { + var primaryMeasure = await _strategicOpportunityService.GetStrategicOpportunityPrimaryMeasure(opportunityId, cancellationToken); + return Ok(primaryMeasure); + } + + [HttpGet("{measureId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task>> GetMeasureDetails(long measureId, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.GetStrategicOpportunityMeasureDetails(measureId, cancellationToken); + return result != null ? Ok(result) : NotFound(); + } + + [HttpGet("{opportunityId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public async Task> GetDrawerDetails(long opportunityId, CancellationToken cancellationToken) + { + var result = await _strategicOpportunityService.GetStrategicOpportunityDrawerDetails(opportunityId, cancellationToken); + return result != null ? Ok(result) : NotFound(); + } + + [HttpPost("{opportunityId}/copy")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> Copy(long opportunityId, [FromBody] string opportunityName, CancellationToken cancellationToken) + { + var newOpportunityId = await _strategicOpportunityService.CopyStrategicOpportunity(opportunityId, opportunityName, cancellationToken); + return newOpportunityId == 0 ? BadRequest() : Ok(newOpportunityId); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/StrategicOpportunitySecurityController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/StrategicOpportunitySecurityController.cs new file mode 100644 index 0000000..3177242 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/StrategicOpportunitySecurityController.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.CoreLib.Claims.Extensions; +using System.Collections.Generic; +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Linq; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class StrategicOpportunitySecurityController : ControllerBase + { + private readonly IStrategicOpportunityService _strategicOpportunityService; + private readonly IOpportunityRoleAssignmentService _opportunityRoleAssignmentService; + + public StrategicOpportunitySecurityController(IStrategicOpportunityService strategicOpportunityService, IOpportunityRoleAssignmentService opportunityRoleAssignmentService) + { + _strategicOpportunityService = strategicOpportunityService; + _opportunityRoleAssignmentService = opportunityRoleAssignmentService; + } + + [HttpGet("{lootId}")] + public async Task GetUserPermissions(long lootId, CancellationToken cancellationToken) + => await _opportunityRoleAssignmentService.GetUserRoleAssignmentsAsync(lootId, cancellationToken); + + [HttpGet("{lootId}/[action]")] + public async Task> GetSimpleSecurities(long lootId, CancellationToken cancellationToken) + => await _opportunityRoleAssignmentService.GetRoleAssignmentsAsync(lootId, cancellationToken); + + [HttpPut("{lootId}/[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> SetSimpleSecurities(long lootId, [FromBody] List permissionSettings, CancellationToken cancellationToken) + { + if (permissionSettings.Any(ps => ps.LootId != lootId)) { return BadRequest(); } + + var roleAssignments = await _opportunityRoleAssignmentService.GetUserRoleAssignmentsAsync(lootId, cancellationToken); + var isSecurable = roleAssignments.CanSecure; + var userGuid = User?.GetUserGuid() ?? Guid.NewGuid(); + if (!User.IsSdtEmployee() && !isSecurable) return Ok(false); + await _opportunityRoleAssignmentService.SaveRoleAssignmentsAsync(lootId, permissionSettings, cancellationToken); + return Ok(true); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/UserSettingsController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/UserSettingsController.cs new file mode 100644 index 0000000..ca1052e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/UserSettingsController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.UserSetting; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class UserSettingsController : ControllerBase + { + private readonly IUserSettingService _userSettingService; + + public UserSettingsController(IUserSettingService userSettingService) + { + _userSettingService = userSettingService; + } + + [HttpGet("{userGuid}")] + public async Task GetUserSetting(Guid userGuid, CancellationToken cancellationToken) + { + return await _userSettingService.GetById(userGuid, cancellationToken); + } + + [HttpPost()] + public async Task SetUserSetting([FromBody] CciUserSetting userSetting, CancellationToken cancellationToken) + { + return await _userSettingService.SetUserSetting(userSetting, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/UsersController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/UsersController.cs new file mode 100644 index 0000000..11ff364 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/UsersController.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.UserService; +using Strata.Users.Client.UserGroups.Models; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class UsersController : ControllerBase + { + private readonly IUserService _userService; + + public UsersController(IUserService users) + { + _userService = users; + } + + [HttpGet("[action]")] + public async Task> GetUsers(CancellationToken cancellationToken) + => await GetUsersByGlobalId("CCI - Basic User", cancellationToken); + + [HttpGet("[action]")] + public async Task> GetOwners(CancellationToken cancellationToken) + => await GetUsers(cancellationToken); + + [HttpGet("[action]")] + public async Task> GetExecutiveSponsors(CancellationToken cancellationToken) + => await GetUsersByGlobalId("CCI - Exec Sponsors", cancellationToken); + + [HttpGet("[action]")] + public async Task> GetCostLeaders(CancellationToken cancellationToken) + => await GetUsersByGlobalId("CCI - Cost Leader", cancellationToken); + + [HttpGet("[action]")] + public async Task> GetAdministrators(CancellationToken cancellationToken) + => await GetUsersByGlobalId("CCI - System Administrator", cancellationToken); + + [HttpGet("[action]")] + public async Task> GetJazzUsers(CancellationToken cancellationToken) + => await _userService.GetJazzUsers(cancellationToken); + + [HttpGet("[action]")] + public async Task> GetUserGroups(CancellationToken cancellationToken) + => await _userService.GetUserGroups(cancellationToken); + + [HttpGet("all-ci-users")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> GetAllCIUsers(CancellationToken cancellationToken) + => await _userService.GetAllCIUsers(cancellationToken); + + private async Task> GetUsersByGlobalId(string globalId, CancellationToken cancellationToken) + => await _userService.GetUsersByGlobalId(globalId, cancellationToken); + + [HttpGet("{userGuid}/[action]")] + public async Task GetUser(Guid userGuid, CancellationToken cancellationToken) + => await _userService.GetUser(userGuid, cancellationToken); + } + +} diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/VariationCaseTypesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/VariationCaseTypesController.cs new file mode 100644 index 0000000..cd18316 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/VariationCaseTypesController.cs @@ -0,0 +1,52 @@ +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes.Filters; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class VariationCaseTypesController : ControllerBase + { + private readonly IUVCaseTypeService _variationCaseTypeService; + + public VariationCaseTypesController(IUVCaseTypeService variationCaseTypeService) + { + _variationCaseTypeService = variationCaseTypeService; + } + + [HttpPost("")] + public async Task> GetCaseTypes(UVCaseTypeLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await _variationCaseTypeService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await _variationCaseTypeService.GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + [HttpPost("{configurationGuid}/[action]")] + public async Task AddPatientPopulationCaseTypes(Guid configurationGuid, List patientPopulations, CancellationToken cancellationToken) + { + return await _variationCaseTypeService.AddPatientPopulationCaseTypes(configurationGuid, User?.Identity?.Name ?? "", patientPopulations, cancellationToken); + } + } + + [ExcludeFromCodeCoverage] + public class UVCaseTypeLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public UVCaseTypeFilters Filters { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Controllers/VariationOpportunitiesController.cs b/src/Strata.ContinuousImprovement.Api/Controllers/VariationOpportunitiesController.cs new file mode 100644 index 0000000..2738cab --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Controllers/VariationOpportunitiesController.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class VariationOpportunitiesController : OpportunityController + { + public VariationOpportunitiesController(IUVOpportunityService opportunityService) + : base(opportunityService) + { + } + + [HttpPost("")] + public async Task> GetOpportunities(UVOpportunityLoadOptions loadOptions, CancellationToken cancellationToken) + { + return await OpportunityService.GetPagedAsync(loadOptions.PagingOptions, loadOptions.Filters, cancellationToken); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Export(UVOpportunityExportOptions exportOptions, CancellationToken cancellationToken) + { + var workbook = await ((IUVOpportunityService)OpportunityService).ExportAsync(exportOptions.SortOptions, exportOptions.Filters, cancellationToken); + return new ExcelContentResult(workbook, "UV_Opportunities"); + + } + + [HttpGet("{configurationGuid}/[action]")] + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + return await ((IUVOpportunityService)OpportunityService).GetFilterOptionsAsync(configurationGuid, cancellationToken); + } + + } + + [ExcludeFromCodeCoverage] + public class UVOpportunityLoadOptions + { + public PagingOptions PagingOptions { get; set; } + public UVFilters Filters { get; set; } + } + + [ExcludeFromCodeCoverage] + public class UVOpportunityExportOptions + { + public SortOptions SortOptions { get; set; } + public UVFilters Filters { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Extensions/ControllerBaseExtensions.cs b/src/Strata.ContinuousImprovement.Api/Extensions/ControllerBaseExtensions.cs new file mode 100644 index 0000000..3e44202 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Extensions/ControllerBaseExtensions.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Strata.ContinuousImprovement.Api.Extensions +{ + public static class ControllerBaseExtensions + { + public static ObjectResult Problem(this ControllerBase controller, ProblemDetails problemDetails) + => controller.Problem(title: problemDetails.Title, + detail: problemDetails.Detail, + statusCode: problemDetails.Status, + instance: problemDetails.Instance, + type: problemDetails.Type); + + public static ObjectResult NotFoundProblem(this ControllerBase controller, ProblemDetails problemDetails = default) + => controller.Problem(title: problemDetails.Title, + detail: problemDetails.Detail, + statusCode: problemDetails?.Status ?? StatusCodes.Status404NotFound, + instance: problemDetails.Instance, + type: problemDetails.Type); + + public static ObjectResult NotFoundProblem(this ControllerBase controller, string title, string detail) + { + var problemDetails = new ProblemDetails + { + Title = title, + Detail = detail, + Status = StatusCodes.Status404NotFound + }; + return controller.Problem(problemDetails); + } + + public static ObjectResult NoContentProblem(this ControllerBase controller, ProblemDetails problemDetails = default) + => controller.Problem(title: problemDetails?.Title, + detail: problemDetails?.Detail, + statusCode: problemDetails?.Status ?? StatusCodes.Status204NoContent, + instance: problemDetails?.Instance, + type: problemDetails?.Type); + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Extensions/MultipartFileControllerExtensions.cs b/src/Strata.ContinuousImprovement.Api/Extensions/MultipartFileControllerExtensions.cs new file mode 100644 index 0000000..544b43d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Extensions/MultipartFileControllerExtensions.cs @@ -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.ContinuousImprovement.Api.Extensions +{ + internal static class MultipartFileControllerExtensions + { + public static async Task 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(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Generics/OpportunityController.cs b/src/Strata.ContinuousImprovement.Api/Generics/OpportunityController.cs new file mode 100644 index 0000000..b5993d6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Generics/OpportunityController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.Generics +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class OpportunityController : ControllerBase where T : class, IOpportunity + { + protected readonly IOpportunityService OpportunityService; + + public OpportunityController(IOpportunityService opportunityService) + { + OpportunityService = opportunityService; + } + + [HttpPut("{opportunityGuid}/[action]")] + public async Task SetHiddenAsync(Guid opportunityGuid, [FromBody] bool isHidden, CancellationToken cancellationToken) + { + await OpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken); + } + + [HttpPut("{opportunityGuid}/[action]")] + public virtual async Task SetNoteAsync(Guid opportunityGuid, [FromBody] string note, CancellationToken cancellationToken) + { + await OpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Generics/Policies.cs b/src/Strata.ContinuousImprovement.Api/Generics/Policies.cs new file mode 100644 index 0000000..7b4f2b5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Generics/Policies.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Api.Generics +{ + public class Policies + { + public const string StrataOnly = "StrataOnly"; + public const string IsAdmin = "IsAdmin"; + } +} diff --git a/src/Strata.ContinuousImprovement.Api/Generics/ProcessingResult.cs b/src/Strata.ContinuousImprovement.Api/Generics/ProcessingResult.cs new file mode 100644 index 0000000..efba7f4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Generics/ProcessingResult.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics.CodeAnalysis; + +namespace Strata.ContinuousImprovement.Api.Generics +{ + [ExcludeFromCodeCoverage] + public class ProcessingResult : StatusCodeResult + { + public ProcessingResult() + : base(StatusCodes.Status102Processing) + { + } + } + +} diff --git a/src/Strata.ContinuousImprovement.Api/Program.cs b/src/Strata.ContinuousImprovement.Api/Program.cs new file mode 100644 index 0000000..ad83480 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Program.cs @@ -0,0 +1,110 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; +using Strata.Configuration.Client.DependencyInjection; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.healthchecks.Biz; +using Strata.Logging.DependencyInjection; +using Strata.Snowflake.Migrations.Client; +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.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(async () => + { + await ExecuteApp(args, doMigrate); + return 0; + }); + commandLineApplication.Execute(args); + } + catch (Exception ex) + { + Log.Error(ex, "There was an unhandled exception that caused the program 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(); + } + } + + public static async Task ExecuteApp(string[] args, CommandOption doMigrate) + { + var webHost = CreateWebHostBuilder(args).Build(); + + if (doMigrate.HasValue()) + { + using var scope = webHost.Services.CreateScope(); + var logger = scope.ServiceProvider.GetRequiredService>(); + + try + { + // Snowflake Migrations + logger.LogInformation("Uploading and applying Snowflake migrations"); + var snowflakeMigrationService = scope.ServiceProvider.GetRequiredService(); + var snowflakeMigrationDirectory = Path.Combine(Environment.CurrentDirectory, "SnowflakeMigrations"); + var success = await snowflakeMigrationService.PackageMigrations(snowflakeMigrationDirectory); + if (!success) + { + logger.LogError("Failed to package migrations."); + Environment.Exit(1); + } + logger.LogInformation("Snowflake migrations successfully uploaded to S3. Begin apply migrations."); + success = await snowflakeMigrationService.ApplyClientMigrations(); + if (!success) + { + logger.LogError("Failed to apply migrations."); + Environment.Exit(1); + } + + // EF Migrations + logger.LogInformation("Applying Entity Framework migrations"); + using var context = scope.ServiceProvider.GetService(); + var hostEnv = scope.ServiceProvider.GetService(); + + context.Database.SetCommandTimeout(TimeSpan.FromMinutes(10)); + context.Database.Migrate(); + logger.LogInformation("All done, closing app"); + Environment.Exit(Environment.ExitCode); + } + catch (Exception ex) + { + logger.LogError(ex, "There was an unhandled exception that caused the program to crash"); + 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(); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/Properties/launchSettings.json b/src/Strata.ContinuousImprovement.Api/Properties/launchSettings.json new file mode 100644 index 0000000..d2c626b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "Strata.ContinuousImprovement.Api": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "", + "applicationUrl": "https://localhost:8443", + "environmentVariables": { + "Hangfire__RunLocally": "true", + "ASPNETCORE_ENVIRONMENT": "Development", + "AWS_S3_US_EAST_1_REGIONAL_ENDPOINT": "regional", + "AWS_Profile": "sdt-continuous-improvement-service-role" + } + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeControllers/EncountersController.cs b/src/Strata.ContinuousImprovement.Api/SnowflakeControllers/EncountersController.cs new file mode 100644 index 0000000..5cf847c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeControllers/EncountersController.cs @@ -0,0 +1,124 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.DecisionSupport.Biz.Parameters; +using Strata.DecisionSupport.Biz.Services; +using Strata.DecisionSupport.Biz.Utilities; +using Strata.DecisionSupport.Models.Encounters; +using Strata.DecisionSupport.Models.Jazz; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.SnowflakeControllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class EncountersController : Controller + { + private readonly IEncounterQueryService _encounterQueryService; + + public EncountersController(IEncounterQueryService encounterQueryService) + { + _encounterQueryService = encounterQueryService; + } + + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] +#pragma warning disable S4457 + public async Task>> GetAsync( + [FromBody] EncounterQueryInfo queryInfo, CancellationToken cancellationToken) +#pragma warning restore + { + if (queryInfo.EntityIDs.IsEmpty()) + return BadRequest(queryInfo); + + return Ok(await _encounterQueryService.RunEncounterInfoQuery(queryInfo, cancellationToken)); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] +#pragma warning disable S4457 + public async Task>> GetCostInfoAsync( + [FromBody] EncounterCostQueryInfo queryInfo, CancellationToken cancellationToken) +#pragma warning restore + { + if (queryInfo.EncounterIds.IsEmpty()) + return BadRequest(queryInfo); + + return Ok(await _encounterQueryService.RunEncounterCostInfoQuery(queryInfo, cancellationToken)); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] +#pragma warning disable S4457 + public async Task>> GetDepartmentUnitOfServiceInfoAsync( + [FromBody] DepartmentEncounterUnitOfServiceQueryInfo queryInfo, CancellationToken cancellationToken) +#pragma warning restore + { + if (queryInfo.EncounterIds.IsEmpty()) + return BadRequest(queryInfo); + + return Ok(await _encounterQueryService.RunDepartmentEncounterUnitOfServiceQuery(queryInfo, cancellationToken)); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] +#pragma warning disable S4457 + public async Task>> GetUnitOfServiceInfoAsync( + [FromBody] EncounterUnitOfServiceQueryInfo queryInfo, CancellationToken cancellationToken) +#pragma warning restore + { + if (queryInfo.EncounterIds.IsEmpty()) + return BadRequest(queryInfo); + + return Ok(await _encounterQueryService.RunEncounterUnitOfServiceQuery(queryInfo, cancellationToken)); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] +#pragma warning disable S4457 + public async Task>> GetEncounterChargeInfoAsync( + [FromBody] EncounterChargeQueryInfo queryInfo, CancellationToken cancellationToken) +#pragma warning restore + { + if (queryInfo.OpportunityGuid.Equals(Guid.Empty)) + return BadRequest(queryInfo); + + return Ok(await _encounterQueryService.RunEncounterChargeQuery(queryInfo, cancellationToken)); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] +#pragma warning disable S4457 + public async Task>> GetVariationEncounterChargeInfoAsync( + [FromBody] VariationEncounterChargeQueryInfo queryInfo, CancellationToken cancellationToken) +#pragma warning restore + { + if (queryInfo.OpportunityGuids.IsEmpty()) + return BadRequest(queryInfo); + + return Ok(await _encounterQueryService.RunVariationEncounterChargeQuery(queryInfo, cancellationToken)); + } + + [HttpPost("los-opportunity-info")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task>> GetLOSOpportunityInfoAsync( + [FromBody] LOSOpportunityQueryInfo queryInfo, CancellationToken cancellationToken) + { + if (queryInfo.OpportunityGuid.Equals(Guid.Empty)) + return BadRequest(queryInfo); + + return Ok(await _encounterQueryService.RunLOSOpportunityInfoQuery(queryInfo, cancellationToken)); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeControllers/UtilizationVariationController.cs b/src/Strata.ContinuousImprovement.Api/SnowflakeControllers/UtilizationVariationController.cs new file mode 100644 index 0000000..77e6c9c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeControllers/UtilizationVariationController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Strata.DecisionSupport.Biz.Parameters; +using Strata.DecisionSupport.Biz.Services; +using Strata.DecisionSupport.Biz.Utilities; +using Strata.DecisionSupport.Models.Jazz; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Api.SnowflakeControllers +{ + [ApiController] + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + public class UtilizationVariationController : Controller + { + private readonly IUtilizationVariationService _utilizationVariationService; + + public UtilizationVariationController(IUtilizationVariationService utilizationVariationService) + { + _utilizationVariationService = utilizationVariationService; + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task>> GetAllChargeInfoAsync( + [FromBody] AllChargeQueryInfo queryInfo, CancellationToken cancellationToken) + { + if (queryInfo.OpportunityGuid.Equals(Guid.Empty)) + return BadRequest(queryInfo); + if (queryInfo.ChargeCodeIds.IsEmpty()) + return BadRequest(queryInfo); + + return Ok(await _utilizationVariationService.RunAllChargeInfoQuery(queryInfo, cancellationToken)); + } + + [HttpPost("[action]")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task>> GetChargeInfoAsync( + [FromBody] ChargeQueryInfo queryInfo, CancellationToken cancellationToken) + { + if (queryInfo.ConfigurationGuid.Equals(Guid.Empty)) + return BadRequest(queryInfo); + if (queryInfo.ChargeCodeIds.IsEmpty()) + return BadRequest(queryInfo); + + return Ok(await _utilizationVariationService.RunChargeInfoQuery(queryInfo, cancellationToken)); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-06-01-00-00 add CCI schema if not exists.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-06-01-00-00 add CCI schema if not exists.sql new file mode 100644 index 0000000..a77f4bb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-06-01-00-00 add CCI schema if not exists.sql @@ -0,0 +1,6 @@ +--Author: sdt-cloveland +--Forward +CREATE SCHEMA IF NOT EXISTS cci; + +--Rollback +--ILB \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-06-24-10-32-CreateCiEncounterCosts.sql.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-06-24-10-32-CreateCiEncounterCosts.sql.sql new file mode 100644 index 0000000..514cf68 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-06-24-10-32-CreateCiEncounterCosts.sql.sql @@ -0,0 +1,43 @@ +--Author: tdebolt +--Forward +CREATE OR REPLACE DYNAMIC TABLE cci.encounterchargecodecosts + TARGET_LAG = DOWNSTREAM --important to set to downstream for creation, manually change to schedule as we enable for clients + WAREHOUSE = 'APP_CI_DYNAMICTABLE_WH' + COMMENT = 'Populate Charge Data by Encounter and Charge Code for use in CI' + REFRESH_MODE = AUTO + INITIALIZE = ON_SCHEDULE +AS +WITH pblidGrouping (pblidrowid, unitsofservice, fixeddirectcost, fixedindirectcost, variabledirectcost, variableindirectcost, totalcost) AS ( + SELECT + cd.pblidrowid, + min(unitsofservice) AS unitsofservice, + sum(fixeddirectcost) as fixeddirectcost, + sum(fixedindirectcost) as fixedindirectcost, + sum(variabledirectcost) as variabledirectcost, + sum(variableindirectcost) as variableindirectcost, + sum(fixeddirectcost) + sum(fixedindirectcost) + sum(variabledirectcost) + sum(variableindirectcost) as totalcost + FROM dss.factpatientcostdetail cd + GROUP BY cd.pblidrowid + ORDER BY cd.pblidrowid +) + +SELECT + d.encounterid, + d.chargecodeid, + sum(d.charge) as charge, + sum(g.unitsofservice) as unitsofservice, + sum(g.fixeddirectcost) as fixeddirectcost, + sum(g.fixedindirectcost) as fixedindirectcost, + sum(g.variabledirectcost) as variabledirectcost, + sum(g.variableindirectcost) as variableindirectcost, + sum(d.fixeddirectunitcost) as fixeddirectunitcost, + sum(d.fixedindirectunitcost) as fixedindirectunitcost, + sum(d.variabledirectunitcost) as variabledirectunitcost, + sum(d.variableindirectunitcost) as variableindirectunitcost, + sum(g.totalcost) as totalcost, +FROM dss.factpatientbillinglineitemdetail d +LEFT JOIN pblidGrouping g on g.pblidrowid = d.rowid +GROUP BY d.encounterid, d.chargecodeid +; +--Rollback +DROP DYNAMIC TABLE IF EXISTS cci.encounterchargecodecosts; diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-06-24-15-06 Add StratetgicOpportunityDetail.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-06-24-15-06 Add StratetgicOpportunityDetail.sql new file mode 100644 index 0000000..f3b049a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-06-24-15-06 Add StratetgicOpportunityDetail.sql @@ -0,0 +1,19 @@ +--Author: sdt-cloveland +--Forward +CREATE SCHEMA IF NOT EXISTS cci; + +CREATE OR REPLACE TABLE cci.StrategicOpportunityDetail ( + OpportunityId BIGINT, + ChargeDepartmentID INT NOT NULL, + EncounterID INT NOT NULL, + ChargeCodeID INT NOT NULL, + EntityID INT NOT NULL, + PhysicianID INT NOT NULL, + PrimaryServiceLineID INT NOT NULL, + PhysicianSpecialtyID INT NOT NULL, + CostDriver STRING NOT NULL, + UnitsOfService FLOAT NOT NULL +); + +--Rollback +DROP TABLE cci.StrategicOpportunityDetail; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-07-09-11-55 Add DimExplorationPopulation table.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-07-09-11-55 Add DimExplorationPopulation table.sql new file mode 100644 index 0000000..3926793 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-07-09-11-55 Add DimExplorationPopulation table.sql @@ -0,0 +1,13 @@ +--Author: sdt-dmoroziuk +--Forward +CREATE OR REPLACE TABLE cci.DimExplorationPopulation ( + ExplorationPopulationId INT NOT NULL, + Name STRING NOT NULL, + CaseTypeFamilyId INT NOT NULL, + CostDriver STRING NOT NULL, + TotalEncounters INT NOT NULL, + TotalVariation FLOAT NOT NULL +); + +--Rollback +DROP TABLE cci.DimExplorationPopulation; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-07-09-12-05 Add FactExplorationPopulationEncounter table.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-07-09-12-05 Add FactExplorationPopulationEncounter table.sql new file mode 100644 index 0000000..7165e60 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-07-09-12-05 Add FactExplorationPopulationEncounter table.sql @@ -0,0 +1,9 @@ +--Author: sdt-dmoroziuk +--Forward +CREATE OR REPLACE TABLE cci.FactExplorationPopulationEncounter ( + ExplorationPopulationId INT NOT NULL, + EncounterId INT NOT NULL +); + +--Rollback +DROP TABLE cci.FactExplorationPopulationEncounter; diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-07-09-12-09 Add POSTGRES_REPLICATION_DATA stage.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-07-09-12-09 Add POSTGRES_REPLICATION_DATA stage.sql new file mode 100644 index 0000000..322bbc0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-07-09-12-09 Add POSTGRES_REPLICATION_DATA stage.sql @@ -0,0 +1,6 @@ +--Author: sdt-dmoroziuk +--Forward +CREATE OR REPLACE STAGE cci.POSTGRES_REPLICATION_DATA; + +--Rollback +DROP STAGE IF EXISTS cci.POSTGRES_REPLICATION_DATA; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-08-05-13-32 Add metrics to StratetgicOpportunityDetail.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-08-05-13-32 Add metrics to StratetgicOpportunityDetail.sql new file mode 100644 index 0000000..3597665 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-08-05-13-32 Add metrics to StratetgicOpportunityDetail.sql @@ -0,0 +1,70 @@ +--Author: sdt-cloveland +--Forward +ALTER TABLE cci.StrategicOpportunityDetail +ADD COLUMN IF NOT EXISTS BaselineCost FLOAT NOT NULL; + +ALTER TABLE cci.StrategicOpportunityDetail +ADD COLUMN IF NOT EXISTS GoalUnits FLOAT NOT NULL; + +ALTER TABLE cci.StrategicOpportunityDetail +ADD COLUMN IF NOT EXISTS GoalCost FLOAT NOT NULL; + +BEGIN + IF ( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_CATALOG = CURRENT_DATABASE() + AND TABLE_SCHEMA = 'CCI' + AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL' + AND COLUMN_NAME = 'UNITSOFSERVICE' + ) + AND NOT EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_CATALOG = CURRENT_DATABASE() + AND TABLE_SCHEMA = 'CCI' + AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL' + AND COLUMN_NAME = 'BASELINEUNITS' + ) + ) + THEN + ALTER TABLE cci.StrategicOpportunityDetail + RENAME COLUMN UnitsOfService TO BaselineUnits; + END IF; +END; + +--Rollback +ALTER TABLE cci.StrategicOpportunityDetail +DROP COLUMN BaselineCost; + +ALTER TABLE cci.StrategicOpportunityDetail +DROP COLUMN GoalCost; + +ALTER TABLE cci.StrategicOpportunityDetail +DROP COLUMN GoalUnits; + +BEGIN + IF ( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_CATALOG = CURRENT_DATABASE() + AND TABLE_SCHEMA = 'CCI' + AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL' + AND COLUMN_NAME = 'BASELINEUNITS' + ) + AND NOT EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_CATALOG = CURRENT_DATABASE() + AND TABLE_SCHEMA = 'CCI' + AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL' + AND COLUMN_NAME = 'UNITSOFSERVICE' + ) + ) + THEN + ALTER TABLE cci.StrategicOpportunityDetail + RENAME COLUMN BaselineUnits TO UnitsOfService; + END IF; +END; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-09-09-10-44-Update CiEncounterCosts Add RowId.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-09-09-10-44-Update CiEncounterCosts Add RowId.sql new file mode 100644 index 0000000..b6112bf --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-09-09-10-44-Update CiEncounterCosts Add RowId.sql @@ -0,0 +1,82 @@ +--Author: tdebolt +--Forward +CREATE OR REPLACE DYNAMIC TABLE cci.encounterchargecodecosts + TARGET_LAG = DOWNSTREAM --important to set to downstream for creation, manually change to schedule as we enable for clients + WAREHOUSE = 'APP_CI_DYNAMICTABLE_WH' + COMMENT = 'Populate Charge Data by Encounter and Charge Code for use in CI' + REFRESH_MODE = AUTO + INITIALIZE = ON_SCHEDULE +AS +WITH pblidGrouping (pblidrowid, unitsofservice, fixeddirectcost, fixedindirectcost, variabledirectcost, variableindirectcost, totalcost) AS ( + SELECT + cd.pblidrowid, + min(unitsofservice) AS unitsofservice, + sum(fixeddirectcost) as fixeddirectcost, + sum(fixedindirectcost) as fixedindirectcost, + sum(variabledirectcost) as variabledirectcost, + sum(variableindirectcost) as variableindirectcost, + sum(fixeddirectcost) + sum(fixedindirectcost) + sum(variabledirectcost) + sum(variableindirectcost) as totalcost + FROM dss.factpatientcostdetail cd + GROUP BY cd.pblidrowid + ORDER BY cd.pblidrowid +) + +SELECT + d.encounterid, + d.chargecodeid, + d.rowid, + sum(d.charge) as charge, + sum(g.unitsofservice) as unitsofservice, + sum(g.fixeddirectcost) as fixeddirectcost, + sum(g.fixedindirectcost) as fixedindirectcost, + sum(g.variabledirectcost) as variabledirectcost, + sum(g.variableindirectcost) as variableindirectcost, + sum(d.fixeddirectunitcost) as fixeddirectunitcost, + sum(d.fixedindirectunitcost) as fixedindirectunitcost, + sum(d.variabledirectunitcost) as variabledirectunitcost, + sum(d.variableindirectunitcost) as variableindirectunitcost, + sum(g.totalcost) as totalcost +FROM dss.factpatientbillinglineitemdetail d +LEFT JOIN pblidGrouping g on g.pblidrowid = d.rowid +GROUP BY d.encounterid, d.chargecodeid, d.rowid +; +--Rollback +CREATE OR REPLACE DYNAMIC TABLE cci.encounterchargecodecosts + TARGET_LAG = DOWNSTREAM --important to set to downstream for creation, manually change to schedule as we enable for clients + WAREHOUSE = 'APP_CI_DYNAMICTABLE_WH' + COMMENT = 'Populate Charge Data by Encounter and Charge Code for use in CI' + REFRESH_MODE = AUTO + INITIALIZE = ON_SCHEDULE +AS +WITH pblidGrouping (pblidrowid, unitsofservice, fixeddirectcost, fixedindirectcost, variabledirectcost, variableindirectcost, totalcost) AS ( + SELECT + cd.pblidrowid, + min(unitsofservice) AS unitsofservice, + sum(fixeddirectcost) as fixeddirectcost, + sum(fixedindirectcost) as fixedindirectcost, + sum(variabledirectcost) as variabledirectcost, + sum(variableindirectcost) as variableindirectcost, + sum(fixeddirectcost) + sum(fixedindirectcost) + sum(variabledirectcost) + sum(variableindirectcost) as totalcost + FROM dss.factpatientcostdetail cd + GROUP BY cd.pblidrowid + ORDER BY cd.pblidrowid +) + +SELECT + d.encounterid, + d.chargecodeid, + sum(d.charge) as charge, + sum(g.unitsofservice) as unitsofservice, + sum(g.fixeddirectcost) as fixeddirectcost, + sum(g.fixedindirectcost) as fixedindirectcost, + sum(g.variabledirectcost) as variabledirectcost, + sum(g.variableindirectcost) as variableindirectcost, + sum(d.fixeddirectunitcost) as fixeddirectunitcost, + sum(d.fixedindirectunitcost) as fixedindirectunitcost, + sum(d.variabledirectunitcost) as variabledirectunitcost, + sum(d.variableindirectunitcost) as variableindirectunitcost, + sum(g.totalcost) as totalcost +FROM dss.factpatientbillinglineitemdetail d +LEFT JOIN pblidGrouping g on g.pblidrowid = d.rowid +GROUP BY d.encounterid, d.chargecodeid + diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-09-30-01-40 Renamed ChargeDepartmentId to DepartmentId of StrategicOpportunityDetail.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-09-30-01-40 Renamed ChargeDepartmentId to DepartmentId of StrategicOpportunityDetail.sql new file mode 100644 index 0000000..1741175 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-09-30-01-40 Renamed ChargeDepartmentId to DepartmentId of StrategicOpportunityDetail.sql @@ -0,0 +1,51 @@ +--Author: sdt-dmoroziuk +--Forward +BEGIN + IF ( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_CATALOG = CURRENT_DATABASE() + AND TABLE_SCHEMA = 'CCI' + AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL' + AND COLUMN_NAME = 'ChargeDepartmentID' + ) + AND NOT EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_CATALOG = CURRENT_DATABASE() + AND TABLE_SCHEMA = 'CCI' + AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL' + AND COLUMN_NAME = 'DepartmentId' + ) + ) + THEN + ALTER TABLE cci.StrategicOpportunityDetail + RENAME COLUMN ChargeDepartmentID TO DepartmentId; + END IF; +END; +--Rollback +BEGIN + IF ( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_CATALOG = CURRENT_DATABASE() + AND TABLE_SCHEMA = 'CCI' + AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL' + AND COLUMN_NAME = 'DepartmentId' + ) + AND NOT EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_CATALOG = CURRENT_DATABASE() + AND TABLE_SCHEMA = 'CCI' + AND TABLE_NAME = 'STRATEGICOPPORTUNITYDETAIL' + AND COLUMN_NAME = 'ChargeDepartmentID' + ) + ) + THEN + ALTER TABLE cci.StrategicOpportunityDetail + RENAME COLUMN DepartmentId TO ChargeDepartmentID; + END IF; +END; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-10-06-06-56 Add columns to cci.StrategicOpportunityDetail.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-10-06-06-56 Add columns to cci.StrategicOpportunityDetail.sql new file mode 100644 index 0000000..c58f65b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-10-06-06-56 Add columns to cci.StrategicOpportunityDetail.sql @@ -0,0 +1,8 @@ +--Author: yrenz +--Forward +ALTER TABLE cci.StrategicOpportunityDetail +ADD COLUMN IF NOT EXISTS CaseTypeFamilyID INT NOT NULL DEFAULT(0); + +--Rollback +ALTER TABLE cci.StrategicOpportunityDetail +DROP COLUMN CaseTypeFamilyID; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-10-17-02-23 Add cci.viewEncounterPatientTypeRollup.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-10-17-02-23 Add cci.viewEncounterPatientTypeRollup.sql new file mode 100644 index 0000000..2ad7cbd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-10-17-02-23 Add cci.viewEncounterPatientTypeRollup.sql @@ -0,0 +1,9 @@ +--Author: yrenz +--Forward +CREATE OR ALTER VIEW cci.viewEncounterPatientTypeRollup AS +SELECT fpes.encounterid, ptr.Type as PatientTypeRollup +FROM clientdss.FactPatientEncounterSummary fpes +INNER JOIN fw.DimPatientType pt ON fpes.PatientTypeID = pt.PatientTypeID +INNER JOIN fw.DimPatientTypeRollup ptr ON pt.PatientTypeRollupID = ptr.PatientTypeRollupID; +--Rollback +DROP VIEW IF EXISTS cci.viewEncounterPatientTypeRollup; diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-11-03-09-00 Update cci.StrategicOpportunityDetail table.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-11-03-09-00 Update cci.StrategicOpportunityDetail table.sql new file mode 100644 index 0000000..a342725 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-11-03-09-00 Update cci.StrategicOpportunityDetail table.sql @@ -0,0 +1,44 @@ +--Author: sdt-mvasylovskyi + +--Forward + +CREATE OR REPLACE TABLE cci.FactStrategicChargeCodeDetail ( + RowId BIGINT AUTOINCREMENT START 1 INCREMENT 1 PRIMARY KEY, + IsBaseline BOOLEAN NOT NULL DEFAULT FALSE, + DischargeFiscalYearId SMALLINT NOT NULL DEFAULT 0, + DischargeFiscalMonthId TINYINT NOT NULL DEFAULT 0, + OpportunityId BIGINT, + EntityId INT NOT NULL, + PrimaryPhysicianId INT NOT NULL, + PrimaryServiceLineId INT NOT NULL, + DepartmentId INT NOT NULL, + PhysicianSpecialtyId INT NOT NULL, + ChargeCodeId INT NOT NULL, + CaseTypeFamilyId INT NOT NULL, + CostDriver VARCHAR(1024) NOT NULL COLLATE 'en-ci', + Units FLOAT NOT NULL, + Cost FLOAT NOT NULL, + GoalUnits FLOAT NOT NULL, + GoalCost FLOAT NOT NULL +); +DROP TABLE IF EXISTS cci.StrategicOpportunityDetail; + +--Rollback + +CREATE OR REPLACE TABLE cci.StrategicOpportunityDetail ( + OpportunityId BIGINT, + EncounterId INT NOT NULL, + EntityId INT NOT NULL, + PhysicianId INT NOT NULL, + PrimaryServiceLineId INT NOT NULL, + DepartmentId INT NOT NULL, + PhysicianSpecialtyId INT NOT NULL, + ChargeCodeId INT NOT NULL, + CaseTypeFamilyId INT NOT NULL, + CostDriver VARCHAR(1024) NOT NULL COLLATE 'en-ci', + BaselineUnits FLOAT NOT NULL, + BaselineCost FLOAT NOT NULL, + GoalUnits FLOAT NOT NULL, + GoalCost FLOAT NOT NULL +); +DROP TABLE IF EXISTS cci.FactStrategicChargeCodeDetail; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-12-22-09-00 Update cci.FactStrategicChargeCodeDetail table.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-12-22-09-00 Update cci.FactStrategicChargeCodeDetail table.sql new file mode 100644 index 0000000..fd1d2f6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2025-12-22-09-00 Update cci.FactStrategicChargeCodeDetail table.sql @@ -0,0 +1,63 @@ +--Author: sdt-mvasylovskyi + +--Forward + +BEGIN +IF ( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA ILIKE 'cci' + AND TABLE_NAME ILIKE 'FactStrategicChargeCodeDetail' + ) +) +THEN + IF ( + NOT EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA ILIKE 'cci' + AND TABLE_NAME ILIKE 'FactStrategicChargeCodeDetail' + AND COLUMN_NAME ILIKE 'DistributedUnits' + ) + ) + THEN + ALTER TABLE cci.FactStrategicChargeCodeDetail + ADD COLUMN DistributedUnits FLOAT NOT NULL DEFAULT(0); + END IF; + + IF ( + NOT EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA ILIKE 'cci' + AND TABLE_NAME ILIKE 'FactStrategicChargeCodeDetail' + AND COLUMN_NAME ILIKE 'DistributedCost' + ) + ) + THEN + ALTER TABLE cci.FactStrategicChargeCodeDetail + ADD COLUMN DistributedCost FLOAT NOT NULL DEFAULT(0); + END IF; +END IF; +END; + +--Rollback + +BEGIN +IF ( + EXISTS ( + SELECT 1 + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA ILIKE 'cci' + AND TABLE_NAME ILIKE 'FactStrategicChargeCodeDetail' + ) +) +THEN + ALTER TABLE cci.FactStrategicChargeCodeDetail + DROP COLUMN IF EXISTS DistributedUnits; + ALTER TABLE cci.FactStrategicChargeCodeDetail + DROP COLUMN IF EXISTS DistributedCost; +END IF; +END; + diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2026-01-23-12-59 Add StrategicOpportunityMeasureDetail table.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2026-01-23-12-59 Add StrategicOpportunityMeasureDetail table.sql new file mode 100644 index 0000000..6aa08ef --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2026-01-23-12-59 Add StrategicOpportunityMeasureDetail table.sql @@ -0,0 +1,12 @@ +--Author: sdt-ydzoba +--Forward +CREATE OR REPLACE TABLE cci.StrategicOpportunityMeasureDetail ( + MeasureId BIGINT NOT NULL, + YearId SMALLINT NOT NULL, + MonthId TINYINT NOT NULL, + Value FLOAT NOT NULL DEFAULT 0, + PRIMARY KEY (MeasureId, YearId, MonthId) +); + +--Rollback +DROP TABLE IF EXISTS cci.StrategicOpportunityMeasureDetail; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2026-03-03-14-00 Add cci.DimStrategicItem.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2026-03-03-14-00 Add cci.DimStrategicItem.sql new file mode 100644 index 0000000..b5f9889 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2026-03-03-14-00 Add cci.DimStrategicItem.sql @@ -0,0 +1,36 @@ +--Author: sdt-tdebolt + +--Forward +CREATE TABLE IF NOT EXISTS cci.dimstrategicitem ( + strategic_item_id NUMBER(38,0) NOT NULL, + name STRING, + rollup_one_id NUMBER(38,0), + rollup_two_id NUMBER(38,0), + rollup_three_id NUMBER(38,0), + baseline_type STRING, + strategic_item_type STRING, + opportunity_type STRING, + status STRING, + baseline_distribution STRING, + baseline_start_date DATE, + baseline_end_date DATE, + estimated_tracking_duration NUMBER(38,0), + tracking_start_date DATE, + tracking_end_date DATE, + estimated_goal NUMBER(19,4), + total_improvement NUMBER(19,4), + cost_leader STRING, + executive_sponsor STRING, + owner STRING, + stakeholders STRING, + team_members STRING, + last_run_tracking_date DATE, + last_run_tracking_status STRING, + + CONSTRAINT pk_dimstrategicitem + PRIMARY KEY (strategic_item_id) +); + +--Rollback + +DROP TABLE IF EXISTS cci.dimstrategicitem; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2026-03-11-10-00 Add cci.FactStrategicGLDetail table.sql b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2026-03-11-10-00 Add cci.FactStrategicGLDetail table.sql new file mode 100644 index 0000000..e8c4dae --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/SnowflakeMigrations/2026-03-11-10-00 Add cci.FactStrategicGLDetail table.sql @@ -0,0 +1,22 @@ +--Author: sdt-mleitch + +--Forward + +CREATE TABLE IF NOT EXISTS cci.FactStrategicGLDetail ( + RowId BIGINT AUTOINCREMENT START 1 INCREMENT 1 PRIMARY KEY, + IsBaseline BOOLEAN NOT NULL DEFAULT FALSE, + FiscalYearId SMALLINT NOT NULL DEFAULT 0, + FiscalMonthId TINYINT NOT NULL DEFAULT 0, + OpportunityId BIGINT NOT NULL, + EntityId INT NOT NULL, + DepartmentId INT NOT NULL, + AccountId INT NOT NULL, + AccountType TINYINT, + Amount FLOAT NOT NULL DEFAULT 0, + GoalAmount FLOAT NOT NULL DEFAULT 0, + DistributedAmount FLOAT NOT NULL DEFAULT 0 +); + +--Rollback + +DROP TABLE IF EXISTS cci.FactStrategicGLDetail; diff --git a/src/Strata.ContinuousImprovement.Api/Startup.cs b/src/Strata.ContinuousImprovement.Api/Startup.cs new file mode 100644 index 0000000..cc9f8e3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Startup.cs @@ -0,0 +1,122 @@ +using Amazon.SQS; +using AutoMapper; +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 Microsoft.Extensions.Hosting; +using Prometheus; +using Strata.ApiLib.Core.Cors.Extensions; +using Strata.ApiLib.Core.ExceptionHandling.DependencyInjection.Bootstrappers; +using Strata.ApiLib.Core.Logging.Bootstrappers; +using Strata.ApiLib.Core.StrataAuthentication.Bootstrappers; +using Strata.ApiLib.Core.Transformers; +using Strata.ContinuousImprovement.Api.Generics; +using Strata.ContinuousImprovement.Biz.BackgroundServices; +using Strata.ContinuousImprovement.Biz.Configurations; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.CoreLib.Claims.Extensions; +using Strata.DecisionSupport.Biz.Configurations; +using Strata.healthchecks.Biz; +using Strata.SignalR.Configuration; +using Strata.SwaggerExtensions; +using System.Diagnostics.CodeAnalysis; +using System.Net.Mime; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Strata.ContinuousImprovement.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.AddGlobalExceptionMiddleware(_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; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }) + .SetCompatibilityVersion(CompatibilityVersion.Version_3_0); + + services.AddAuthorization(options => + { + options.AddPolicy(Policies.IsAdmin, + policy => policy.RequireAssertion(context => context.User.IsInRole("CI - System Administrator") || context.User.IsSdtEmployee())); + options.AddPolicy(Policies.StrataOnly, + policy => policy.RequireAssertion(context => context.User.IsSdtEmployee())); + }); + + services.AddStrataAuthentication(_configuration); + services.AddStrataHangfire(); + services.AddContinuousImprovementServices(_configuration); + services.AddDecisionSupportServices(_configuration); + services.AddRouting(); + services.AddSwagger(_configuration); + services.AddStrataSignalR(); + services.AddStrataHealthChecks(_configuration); + services.AddSnowflakeMigrationServiceClient(); + services.AddStrataCors(_configuration); + services.AddHealthChecks(); + + services.AddTransient(); + services.AddHostedService(); + } + + // 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.UseGlobalExceptionMiddleware(); + app.UseRouting(); + app.UseStrataCorsPolicy(env); + + if (!env.IsDevelopment()) + { + app.UseHsts(); + app.UseHttpsRedirection(); + } + + app.UseMetricServer(); + app.UseHttpMetrics(); + app.UseAccessTokenQueryStringMiddleware(); + app.UseStrataAuthentication(); + app.UseClaimsLogging(); + app.ConfigureSwagger(env, provider); + app.UseEndpoints(endpoints => endpoints.MapControllers().RequireAuthorization()); + app.UseEndpoints(endpoints => endpoints.MapHangfireHub()); + app.UseEndpoints(endpoints => endpoints.MapNotificationHub()); + app.UseStrataHealthChecks(env); + } + } + +} diff --git a/src/Strata.ContinuousImprovement.Api/Strata.ContinuousImprovement.Api.csproj b/src/Strata.ContinuousImprovement.Api/Strata.ContinuousImprovement.Api.csproj new file mode 100644 index 0000000..16ebccc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/Strata.ContinuousImprovement.Api.csproj @@ -0,0 +1,77 @@ + + + + net8.0 + InProcess + + + + true + $(NoWarn);1591 + true + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_Parameter1>Strata.ContinuousImprovement.Api.Test.Unit + + + + + + PreserveNewest + + + + diff --git a/src/Strata.ContinuousImprovement.Api/appsettings.Development.json b/src/Strata.ContinuousImprovement.Api/appsettings.Development.json new file mode 100644 index 0000000..1a039f2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/appsettings.Development.json @@ -0,0 +1,37 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "config": { + "sqsReplicationCompleteQueueName": "sdt-continuous-improvement-replication-status-queue", + "sqsEnabled": true + }, + "hangfire": { + "url": "https://localhost:44369", + "redis": { + "prefix": "continuousimprovement-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" + } + }, + "accessGudid": { + "url": "https://accessgudid.nlm.nih.gov/download/delimited" + }, + "datadog": { + "seedgmdnjob": "https://app.datadoghq.com/logs/livetail?query=service%3ASeedGMDNTermDataJob%20env%3Adev&cols=status%2Cenv%2C%40db.strata_id%2C%40db.org_pin%2C%40db.name%2C%40db.instance%2C%40process.name%2Cservice&index=%2A&messageDisplay=inline&stream_sort=desc&viz=stream&from_ts=1692207497147&to_ts=1692293897147&live=true" + }, + "strataApi": { + "clientSecret": "V_okTrkWYBs-PqMP.inbJShIvMGWcpdqCDq_ZjntVF.ydgRCoYaDVJYEWEIdsjJ_" + }, + "ChargeCodeOpportunityValidation": { + "maxChargeCodeSelections": 100, + "maxOpportunityLevelSelections": 10 + }, + "Snowflake": { + "Url": "https://stratadev.us-east-1.privatelink.snowflakecomputing.com", + "Account": "stratadev" + } +} diff --git a/src/Strata.ContinuousImprovement.Api/appsettings.json b/src/Strata.ContinuousImprovement.Api/appsettings.json new file mode 100644 index 0000000..1416272 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/appsettings.json @@ -0,0 +1,42 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*", + "Version": "0.0.0", + "configuration": { + "basicAuthUsername": "BkQsRDdmdjkFNhKVuTI.TpujQJXuKhGDcV.F_hjADMRTaEUWMJnviaesoOrhhfnz", + "basicAuthPassword": "GWPIIWECDqh.hjDNdpxVEfeFe-bYFwPSx-oBGFD_od_SUQVk-QqTIkZY_NXMeqeo" + }, + "config": { + "sqsReplicationCompleteQueueName": "sdt-continuous-improvement-replication-status-queue", + "sqsEnabled": true + }, + "endpoints": { + "dataschema": "https://dataschema-api.dev.stratanetwork.net/", + "schema": "https://schema-api.dev.stratanetwork.net", + "analytics": "https://analytics-api.dev.stratanetwork.net" + }, + "StrataConfigServerBaseUrl": "https://configuration.dev.stratanetwork.net", + "NetworkOpportunity": { + "Creators": [ "" ] + }, + "HealthCheck": { + "PushToDataDog": true, + "StrataJazz": true, + "JazzDatabaseGuid": "54409f55-5ddf-4748-8222-9ccfb04fb4f5", + "Snowflake": true, + "Redis": true, + "Sqs": false, + "Sns": false, + "PostgresDatabaseName": "continuousimprovement", + "SqsQueueNames": [], + "SnsTopicArns": [] + }, + "Snowflake": { + "Url": "https://stratadev.us-east-1.privatelink.snowflakecomputing.com", + "Account": "stratadev" + } +} diff --git a/src/Strata.ContinuousImprovement.Api/runtimeconfig.template.json b/src/Strata.ContinuousImprovement.Api/runtimeconfig.template.json new file mode 100644 index 0000000..9cbaf60 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Api/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.Drawing.EnableUnixSupport": true + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/All/Opportunities/AllOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/AllOpportunity.cs new file mode 100644 index 0000000..660cb97 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/AllOpportunity.cs @@ -0,0 +1,16 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.All.Opportunities +{ + [Table("viewAllOpportunity", Schema = "cci")] + public class AllOpportunity : Opportunity + { + public string Name { get; set; } + public int OpportunityType { get; set; } + public string OpportunityTypeName { get; set; } + public string CostDriver { get; set; } + public long OpportunityId { get; set; } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/All/Opportunities/AllOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/AllOpportunityService.cs new file mode 100644 index 0000000..10e13bc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/AllOpportunityService.cs @@ -0,0 +1,242 @@ +using ClosedXML.Excel; +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.All.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.Exploration.Opportunities; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities; +using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.All.Opportunities +{ + public class AllOpportunityService : OpportunityService, IAllOpportunityService + { + private readonly IUVOpportunityService _uvOpportunityService; + private readonly IQVIOpportunityService _qviOpportunityService; + private readonly IGLOpportunityService _glOpportunityService; + private readonly IFFOpportunityService _ffOpportunityService; + private readonly IPROpportunityService _prOpportunityService; + private readonly IENOpportunityService _enOpportunityService; + private readonly ICCOpportunityService _ccOpportunityService; + private readonly IEXOpportunityService _blankOpportunityService; + + internal AllOpportunityService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IInitiativeService initiativeService, IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) + { + + } + +#pragma warning disable S107 // Constructor has NN new parameters, which is greater than the 7 authorized + public AllOpportunityService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IInitiativeService initiativeService, IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + IUVOpportunityService uvOpportunityService, + IQVIOpportunityService qviOpportunityService, + IGLOpportunityService glOpportunityService, + IFFOpportunityService ffOpportunityService, + IPROpportunityService prOpportunityService, + IENOpportunityService enOpportunityService, + ICCOpportunityService ccOpportunityService, + IEXOpportunityService blankOpportunityService) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) +#pragma warning restore S107 + { + _uvOpportunityService = uvOpportunityService; + _qviOpportunityService = qviOpportunityService; + _glOpportunityService = glOpportunityService; + _ffOpportunityService = ffOpportunityService; + _prOpportunityService = prOpportunityService; + _enOpportunityService = enOpportunityService; + _ccOpportunityService = ccOpportunityService; + _blankOpportunityService = blankOpportunityService; + } + + public async Task ContainsAsync(Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return false; + + return await dbContext.AllOpportunities.ContainsAsync(new AllOpportunity() { OpportunityGuid = opportunityGuid }, cancellationToken); + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return null; + + var opportunityContext = dbContext.Set(); + var configuredOpportunities = opportunityContext.Where(en => en.ConfigurationGuid == configurationGuid); + var opportunityTypeIds = Enum.GetValues().Cast(); + + var opportunityTypes = configuredOpportunities + .Where(ol => opportunityTypeIds.Contains((byte)ol.OpportunityType)) + .Select(ol => new FilterMember + { + Id = ol.OpportunityType.ToString(), + Text = ((OpportunityTypes)ol.OpportunityType).OpportunityTypeName() + }) + .Distinct().ToList().OrderBy(ol => ol.Text); + + return new AllFilterOptions + { + OpportunityTypes = opportunityTypes + }; + } + + public async Task ExportAsync(SortOptions sortOptions, AllFilters filters, CancellationToken cancellationToken) + { + var exportOptions = this.GetExportOptions(); + + // get data + var opportunities = await this.GetAllAsync(sortOptions, filters, cancellationToken); + var data = opportunities.Select(opp => new + { + Opportunity = opp.OpportunityKey, + Type = opp.OpportunityTypeName, + opp.Name, + opp.IdentifiedSavings, + opp.Initiative, + IsHidden = opp.IsHidden ? 1 : 0, + opp.Note + }); + + // configure column formats + exportOptions.AddColumnOptions("Identified Savings", "Currency2Dec"); + exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\""); + + return ExportUtils.CreateExcelWorkbook(data, exportOptions); + } + + public async Task NameIsValid(Guid configurationGuid, int opportunityType, string name, Guid? opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return default; + var opportunityContext = dbContext.Set(); + var opportunityNames = await opportunityContext + .Where(opp => opp.ConfigurationGuid == configurationGuid + && opp.OpportunityType == opportunityType + && opp.Name.StartsWith(name) + && opp.OpportunityGuid != opportunityGuid // don't check against current opportunity if editing + ).Select(opp => opp.Name).ToListAsync(cancellationToken); + // FreeForm has the Improvement Type in the All name + var nameRegex = Regex.Replace(name, @"([()[\]!@#$%^&*])", @"\$1"); + var regex = opportunityType == (int)OpportunityTypes.FreeForm + ? new Regex(@$"^{nameRegex} \(.+\) - FY[\d]{{4}}$", RegexOptions.IgnoreCase) + : new Regex(@$"^{nameRegex} - FY[\d]{{4}}$", RegexOptions.IgnoreCase); + + var nameIsValid = !opportunityNames.Exists(opporturnityName => regex.IsMatch(opporturnityName)); + + return nameIsValid; + } + + public override async Task SetHiddenAsync(Guid opportunityGuid, bool isHidden, CancellationToken cancellationToken) + { + var opportunityItem = await GetById(opportunityGuid, cancellationToken); + if (opportunityItem == null) return; + switch ((OpportunityTypes)opportunityItem.OpportunityType) + { + case OpportunityTypes.Variation: + await _uvOpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken); + return; + case OpportunityTypes.QualityVariation: + await _qviOpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken); + return; + case OpportunityTypes.FreeForm: + await _ffOpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken); + return; + case OpportunityTypes.GeneralLedger: + await _glOpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken); + return; + case OpportunityTypes.Payroll: + await _prOpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken); + return; + case OpportunityTypes.Encounter: + await _enOpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken); + return; + case OpportunityTypes.ChargeCode: + await _ccOpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken); + return; + case OpportunityTypes.Blank: + await _blankOpportunityService.SetHiddenAsync(opportunityGuid, isHidden, cancellationToken); + return; + case OpportunityTypes.Network: + var dbContext = await GetDbContext(cancellationToken); + var nol = await dbContext.NetworkOpportunityLinks + .SingleOrDefaultAsync(n => n.OpportunityGuid == opportunityGuid); + if (nol != null) + { + nol.IsHidden = isHidden; + } + dbContext.Update(nol); + await dbContext.SaveChangesAsync(cancellationToken); + return; + } + } + + public override async Task SetNoteAsync(Guid opportunityGuid, string note, CancellationToken cancellationToken) + { + var opportunityItem = await GetById(opportunityGuid, cancellationToken); + if (opportunityItem == null) return; + switch ((OpportunityTypes)opportunityItem.OpportunityType) + { + case OpportunityTypes.Variation: + await _uvOpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken); + return; + case OpportunityTypes.QualityVariation: + await _qviOpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken); + return; + case OpportunityTypes.FreeForm: + await _ffOpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken); + return; + case OpportunityTypes.GeneralLedger: + await _glOpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken); + return; + case OpportunityTypes.Payroll: + await _prOpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken); + return; + case OpportunityTypes.Encounter: + await _enOpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken); + return; + case OpportunityTypes.ChargeCode: + await _ccOpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken); + return; + case OpportunityTypes.Blank: + await _blankOpportunityService.SetNoteAsync(opportunityGuid, note, cancellationToken); + return; + case OpportunityTypes.Network: + var dbContext = await GetDbContext(cancellationToken); + var nol = await dbContext.NetworkOpportunityLinks + .SingleOrDefaultAsync(n => n.OpportunityGuid == opportunityGuid); + if (nol != null) + { + nol.Note = note; + } + dbContext.Update(nol); + await dbContext.SaveChangesAsync(cancellationToken); + return; + } + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/All/Opportunities/Filters/AllFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/Filters/AllFilterOptions.cs new file mode 100644 index 0000000..04a6a78 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/Filters/AllFilterOptions.cs @@ -0,0 +1,10 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.All.Opportunities.Filters +{ + public class AllFilterOptions + { + public IEnumerable OpportunityTypes { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/All/Opportunities/Filters/AllFilters.cs b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/Filters/AllFilters.cs new file mode 100644 index 0000000..e07ac18 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/Filters/AllFilters.cs @@ -0,0 +1,48 @@ +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.All.Opportunities.Filters +{ + public class AllFilters : FiltersOpportunity + { + public IEnumerable OpportunityTypes { get; set; } + public bool HasFlexibleExploration { get; set; } + + public AllFilters() : base() + { + OpportunityTypes = new List(); + } + + public override IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = base.GetFilters(context).ToList(); + + if (OpportunityTypes.Any()) + { + filters.Add(m => OpportunityTypes.Contains(m.OpportunityType.ToString())); + } + else + { + var opportunityTypeIds = Enum.GetValues().Cast().ToList(); + filters.Add(m => opportunityTypeIds.Contains((byte)m.OpportunityType)); + } + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + filters.Add(m => m.OpportunityKey.Contains(OpportunitySearch) + || m.Name.Contains(OpportunitySearch) + || m.OpportunityTypeName.Contains(OpportunitySearch) + || m.CostDriver.Contains(OpportunitySearch)); + } + if (!HasFlexibleExploration) + { + filters.Add(m => !(m.OpportunityType == (int)Generics.OpportunityTypes.Blank)); + } + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/All/Opportunities/IAllOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/IAllOpportunityService.cs new file mode 100644 index 0000000..41a5b3e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/All/Opportunities/IAllOpportunityService.cs @@ -0,0 +1,21 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.All.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.All.Opportunities +{ + public interface IAllOpportunityService : IOpportunityService + { + Task ContainsAsync(Guid opportunityGuid, CancellationToken cancellationToken); + + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + + Task ExportAsync(SortOptions sortOptions, AllFilters filters, CancellationToken cancellationToken); + + Task NameIsValid(Guid configurationGuid, int opportunityType, string name, Guid? opportunityGuid, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/All/OpportunityTypeExtensions.cs b/src/Strata.ContinuousImprovement.Biz/All/OpportunityTypeExtensions.cs new file mode 100644 index 0000000..9e79d52 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/All/OpportunityTypeExtensions.cs @@ -0,0 +1,24 @@ +using Strata.ContinuousImprovement.Biz.Generics; + +namespace Strata.ContinuousImprovement.Biz.All +{ + public static class OpportunityTypeExtensions + { + public static string OpportunityTypeName(this OpportunityTypes opportunityType) + { + return opportunityType switch + { + OpportunityTypes.Variation => "Utilization Variation", + OpportunityTypes.QualityVariation => "Quality Variation", + OpportunityTypes.FreeForm => "Free Form", + OpportunityTypes.GeneralLedger => "General Ledger", + OpportunityTypes.Payroll => "Payroll", + OpportunityTypes.Encounter => "Encounter", + OpportunityTypes.ChargeCode => "Charge Code", + OpportunityTypes.Blank => "Exploration", + OpportunityTypes.Exploration => "Exploration", + _ => "" + }; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/BackgroundClientJob/BackgroundClientJobService.cs b/src/Strata.ContinuousImprovement.Biz/BackgroundClientJob/BackgroundClientJobService.cs new file mode 100644 index 0000000..8122622 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/BackgroundClientJob/BackgroundClientJobService.cs @@ -0,0 +1,44 @@ +using Hangfire; +using System.Threading; + +namespace Strata.ContinuousImprovement.Biz.BackgroundClientJob +{ + public class BackgroundClientJobService : IBackgroundClientJobService + { + private readonly IBackgroundJobClient _backgroundJobClient; + + public BackgroundClientJobService(IBackgroundJobClient backgroundJobClient) + { + _backgroundJobClient = backgroundJobClient; + } + + public string GetJobStatus(string jobId, CancellationToken cancellationToken) + { + // Ensure Hangfire DI components are realized. + EnsureHangfireInitialized(); + + if (string.IsNullOrWhiteSpace(jobId)) + { + return "NotFound"; + } + + try + { + var connection = JobStorage.Current.GetConnection(); + var jobData = connection.GetJobData(jobId); + if (jobData == null) + { + return "NotFound"; + } + return jobData.State ?? "Unknown"; + } + catch + { + return "Error"; + } + } + + private void EnsureHangfireInitialized() => + _ = _backgroundJobClient; + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/BackgroundClientJob/IBackgroundClientJobService.cs b/src/Strata.ContinuousImprovement.Biz/BackgroundClientJob/IBackgroundClientJobService.cs new file mode 100644 index 0000000..1373696 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/BackgroundClientJob/IBackgroundClientJobService.cs @@ -0,0 +1,12 @@ +using System.Threading; + +namespace Strata.ContinuousImprovement.Biz.BackgroundClientJob +{ + public interface IBackgroundClientJobService + { + // Returns the Hangfire job state string. + // Possible values: Hangfire states (Enqueued, Scheduled, Processing, Succeeded, Failed, Deleted) + // or the sentinel values: NotFound, Unknown, Error + string GetJobStatus(string jobId, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/BackgroundServices/ReplicationBackgroundService.cs b/src/Strata.ContinuousImprovement.Biz/BackgroundServices/ReplicationBackgroundService.cs new file mode 100644 index 0000000..15467f9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/BackgroundServices/ReplicationBackgroundService.cs @@ -0,0 +1,140 @@ +using Amazon.SQS; +using Amazon.SQS.Model; +using Hangfire; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Replication.Client.DTOs; +using Strata.Replication.Client.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.BackgroundServices +{ + public class ReplicationBackgroundService : BackgroundService + { + private readonly IConfiguration _config; + private readonly ILogger _logger; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly IAmazonSQS _sqsClient; + private readonly List _strategicOpportunityTables = new List + { + "dss.FactPatientBillingLineItemDetail", + "clientdss.FactPatientEncounterSummary" + }; + + private string SqsQueueName { get { return "config:sqsReplicationCompleteQueueName"; } } + + public ReplicationBackgroundService( + IConfiguration config, + ILogger logger, + IAmazonSQS sqsClient, + IBackgroundJobClient backgroundJobClient) + { + _config = config; + _logger = logger; + _sqsClient = sqsClient; + _backgroundJobClient = backgroundJobClient; + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + try + { + var sqsEnabled = _config.GetValue("config:sqsEnabled"); + if (!sqsEnabled) + { + _logger.LogInformation("SQS is not enabled. Exiting."); + return; + } + + _logger.LogInformation("Connecting to SQS queue {sqsQueueName}", SqsQueueName); + var queueUrlResponse = await _sqsClient.GetQueueUrlAsync(_config.GetValue(SqsQueueName), cancellationToken); + _logger.LogInformation("Found SQS queue {queueUrl}", queueUrlResponse.QueueUrl); + + while (!cancellationToken.IsCancellationRequested) + { + var messageRequest = new ReceiveMessageRequest + { + MessageSystemAttributeNames = { "senttimestamp" }, + MaxNumberOfMessages = 10, + MessageAttributeNames = { "all" }, + QueueUrl = queueUrlResponse.QueueUrl, + WaitTimeSeconds = 20 + }; + + var messageResponse = await _sqsClient.ReceiveMessageAsync(messageRequest, cancellationToken); + _logger.LogInformation("Received response on queue {queueUrl} with count of messages {messageCount} and response code {httpStatusCode}", queueUrlResponse.QueueUrl, messageResponse.Messages.Count, messageResponse.HttpStatusCode); + + if (messageResponse.HttpStatusCode != System.Net.HttpStatusCode.OK) + { + continue; + } + + await ProcessMessagesAsync(queueUrlResponse.QueueUrl, messageResponse.Messages, cancellationToken); + } + } + catch (Exception ex) + { + _logger.LogError("Error while processing {sqsQueueName}: {ex}", SqsQueueName, ex); + throw; + } + } + + protected async Task ProcessMessagesAsync(string queueUrl, List messages, CancellationToken cancellationToken) + { + foreach (var message in messages) + { + try + { + _logger.LogInformation("Processing message for {queueUrl}: {message}", queueUrl, message); + if (string.IsNullOrEmpty(message.Body)) + return; + + JToken token = JObject.Parse(message.Body); + var visibiltyRequest = new ChangeMessageVisibilityRequest(queueUrl, message.ReceiptHandle, 720); //set it invisible for 2 hours or until job completes then delete + var messageDetail = JsonConvert.DeserializeObject(token.SelectToken("Message")?.ToString()!); + if (messageDetail.EndTime == null || messageDetail.RowCount == 0 || messageDetail.ReplicationType == ReplicationTaskType.Column) + return; + + if (_strategicOpportunityTables.Any(t => t.Equals(messageDetail.TableName))) + { + await _sqsClient.ChangeMessageVisibilityAsync(visibiltyRequest, cancellationToken); + _logger.LogInformation("Enqueuing StrategicOpportunityChargeCodeJob for {databaseGuid}", messageDetail.DatabaseGuid); + _backgroundJobClient.Enqueue(job => job.RunTracking(messageDetail.DatabaseGuid, messageDetail.StrataId, cancellationToken)); + } + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "Error processing SQS message", new Dictionary { { "queueUrl", queueUrl } }); + } + finally + { + await DeleteMessageAsync(queueUrl, message.ReceiptHandle, cancellationToken); + } + } + } + + protected async Task DeleteMessageAsync(string queueUrl, string msgReceiptHandle, CancellationToken cancellationToken) + { + try + { + var deleteRequest = new DeleteMessageRequest(queueUrl, msgReceiptHandle); + _logger.LogInformation("Deleting message for {queueUrl}: {deleteRequest}", queueUrl, deleteRequest); + + await _sqsClient.DeleteMessageAsync(deleteRequest, cancellationToken); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "Error deleting SQS message", new Dictionary { { "queueUrl", queueUrl } }); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/ChargeCode.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/ChargeCode.cs new file mode 100644 index 0000000..1352aa9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/ChargeCode.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode +{ + [Table("DimChargeCode", Schema = "fw")] + public class ChargeCode + { + public int ChargeCodeId { get; set; } + public string Rollup { get; set; } + public string CostDriver { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/ChargeCodeService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/ChargeCodeService.cs new file mode 100644 index 0000000..afb53d1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/ChargeCodeService.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.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.ContinuousImprovement.Biz.ChargeCode +{ + public class ChargeCodeService : IChargeCodeService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ILogger _logger; + + public ChargeCodeService(IAsyncDbContextFactory dbContextFactory, ILogger logger) + { + _dbContextFactory = dbContextFactory; + _logger = logger; + } + + private async Task GetDbContextAsync(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task> GetChargeCodeCostDrivers(CancellationToken cancellationToken) + { + try + { + var dbContext = await GetDbContextAsync(cancellationToken); + return await dbContext.ChargeCodes.Select(x => + !string.IsNullOrEmpty(x.CostDriver) ? x.CostDriver : "Not Specified") + .Distinct().OrderBy(x => x).ToListAsync(cancellationToken); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + } + + return new List(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/ChargeCodeValidation.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/ChargeCodeValidation.cs new file mode 100644 index 0000000..4e5ab0d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/ChargeCodeValidation.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.ChargeCode +{ + public class ChargeCodeOpportunityValidationSection + { + public int MaxChargeCodeSelections { get; set; } + public int MaxOpportunityLevelSelections { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Filters/CCFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Filters/CCFilterOptions.cs new file mode 100644 index 0000000..3afe470 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Filters/CCFilterOptions.cs @@ -0,0 +1,10 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters +{ + public class CCFilterOptions + { + public IEnumerable OpportunityLevels { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Filters/CCFilters.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Filters/CCFilters.cs new file mode 100644 index 0000000..c504f16 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Filters/CCFilters.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters +{ + public partial class CCFilters : FiltersOpportunity + { + public int Cases { get; set; } + public IEnumerable OpportunityLevels { get; set; } + public ChargeCodeGroup ChargeCodeGroups { get; set; } + public SeasonalityType Seasonality { get; set; } + + public CCFilters() : base() + { + Cases = 0; + OpportunityLevels = new List(); + ChargeCodeGroups = ChargeCodeGroup.Both; + Seasonality = SeasonalityType.WithAndWithout; + } + + public override IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = base.GetFilters(context).ToList(); + + if (Cases > 0) + filters.Add(m => m.Cases >= Cases); + + if (OpportunityLevels.Any()) + filters.Add(m => OpportunityLevels.Contains(m.OpportunityLevel)); + + if (ChargeCodeGroups != ChargeCodeGroup.Both) + filters.Add(m => (int)ChargeCodeGroups == (int)m.ChargeCodeGroup); + + if (Seasonality != SeasonalityType.WithAndWithout) + filters.Add(m => (int)Seasonality == Convert.ToInt32(m.IsSeasonal)); + + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + var testSeasonality = Enum.TryParse(OpportunitySearch, true, out var seasonalityType) + && seasonalityType != SeasonalityType.WithAndWithout; + filters.Add(m => m.OpportunityKey.Contains(OpportunitySearch) + || m.Name.Contains(OpportunitySearch) + || m.ChargeCodeGroupValue.Contains(OpportunitySearch) + || m.OpportunityLevel.Contains(OpportunitySearch) + || (testSeasonality && m.IsSeasonal == (seasonalityType == SeasonalityType.With))); + } + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/IChargeCodeService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/IChargeCodeService.cs new file mode 100644 index 0000000..cba3a8d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/IChargeCodeService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode +{ + public interface IChargeCodeService + { + Task> GetChargeCodeCostDrivers(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Initiatives/CCInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Initiatives/CCInitiativeService.cs new file mode 100644 index 0000000..15fd576 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Initiatives/CCInitiativeService.cs @@ -0,0 +1,243 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Hangfire.Jazz.Client; +using Strata.Hangfire.Jazz.Client.Models; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives +{ + public class CCInitiativeService : ICCInitiativeService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ICCOpportunityService _opportunityService; + private readonly IInitiativeSavingsService _initiativeSavingsService; + private readonly ICCOpportunityDetailService _ccOpportunityDetailService; + private readonly IAuthorizationServiceClient _authorizationServiceClient; + private readonly IJazzHangfireServiceClient _hangfireServiceClient; + private readonly IUpdateFactOpportunitySavingsService _updateFactOpportunitySavingsService; + private readonly ILogger _logger; + +#pragma warning disable S107 // + public CCInitiativeService( + IAsyncDbContextFactory dbContextFactory, + ICCOpportunityService opportunityService, + IInitiativeSavingsService initiativeSavingsService, + ICCOpportunityDetailService ccOpportunityDetailService, + IAuthorizationServiceClient authorizationServiceClient, + IJazzHangfireServiceClient hangfireServiceClient, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger logger) +#pragma warning restore + { + _dbContextFactory = dbContextFactory; + _opportunityService = opportunityService; + _initiativeSavingsService = initiativeSavingsService; + _ccOpportunityDetailService = ccOpportunityDetailService; + _authorizationServiceClient = authorizationServiceClient; + _hangfireServiceClient = hangfireServiceClient; + _updateFactOpportunitySavingsService = updateFactOpportunitySavingsService; + _logger = logger; + } + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task CreateChargeCodeInitiative(Guid dbGuid, Initiative initiative, Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunity = await dbContext.ChargeCodeOpportunities.SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunity == null) { return Guid.Empty; } + + try + { + opportunity.LevelCases = await dbContext.ChargeCodeOpportunityLevelCases + .Where(cases => cases.OpportunityGuid == opportunity.OpportunityGuid && cases.IsBaseline).ToListAsync(); + + _initiativeSavingsService.ConfigureRampUps(dbContext, initiative, opportunity.ConfigurationGuid); + + var details = dbContext.ChargeCodeDetails.Where(detail => detail.OpportunityGUID == opportunityGuid && detail.IsBaseline).AsEnumerable(); + double identifiedSavings = _ccOpportunityDetailService.CalculateSavings(details); + + opportunity.IdentifiedSavings = identifiedSavings; + + var updateInitiative = await dbContext.Initiatives.SingleOrDefaultAsync(x => x.InitiativeGUID == opportunity.InitiativeGuid); + if (updateInitiative != null) + { + updateInitiative.Update(initiative); + await dbContext.SaveChangesAsync(cancellationToken); + + // refreshing tracking data also populates the initiative savings values + await _opportunityService.RefreshOpportunityTrackingData(dbGuid, opportunityGuid, cancellationToken); + } + else + { + opportunity.InitiativeGuid = initiative.InitiativeGUID; + initiative.ConfigurationGUID = opportunity.ConfigurationGuid; + await dbContext.Initiatives.AddAsync(initiative, cancellationToken); + } + await dbContext.SaveChangesAsync(cancellationToken); + + // refreshing tracking data also populates the initiative savings values + await _opportunityService.RefreshOpportunityTrackingData(dbGuid, opportunityGuid, cancellationToken); + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(initiative.InitiativeGUID, SecurableEntityType.Initiative, cancellationToken); + await _authorizationServiceClient.AddPermissionAsync(initiative.InitiativeGUID, initiative.OwnerGUID, true, false, false, cancellationToken); + + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + + return opportunity.InitiativeGuid; + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, opportunityGuid, initiative.InitiativeGUID); + return Guid.Empty; + } + } + + public async Task UpdateChargeCodeInitiative(Guid dbGuid, Initiative initiative, Guid opportunityGuid, bool wasDetailsChanged, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunity = await dbContext.ChargeCodeOpportunities.SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + + if (opportunity == null) { return Guid.Empty; } + try + { + var details = dbContext.ChargeCodeDetails.Where(detail => detail.OpportunityGUID == opportunityGuid && detail.IsBaseline).AsEnumerable(); + double identifiedSavings = _ccOpportunityDetailService.CalculateSavings(details); + opportunity.IdentifiedSavings = identifiedSavings; + + var updateInitiative = await dbContext.Initiatives.SingleOrDefaultAsync(x => x.InitiativeGUID == initiative.InitiativeGUID); + if (updateInitiative == null) { return Guid.Empty; } + + updateInitiative.Update(initiative); + await dbContext.SaveChangesAsync(cancellationToken); + + if (!wasDetailsChanged) + { + return Guid.Empty; + } + // refreshing tracking data also populates the initiative savings values + await _opportunityService.RefreshOpportunityTrackingData(dbGuid, opportunityGuid, cancellationToken); + + // Finalized initiatives need tracking re-run + if (updateInitiative.CurrentStep == (int)InitiativeSteps.Tracking) + { + var job = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.Custom.ChargeCode.RunChargeCodeTrackingTask, Strata.CS.Jazz.Biz.CCI", + ContextData = JsonConvert.SerializeObject(new + { + configurationGuid = opportunity.ConfigurationGuid, + initiativeGuid = initiative.InitiativeGUID + }) + }; + + await _hangfireServiceClient.EnqueueJobAsync(job, cancellationToken) + .ContinueWith(job => new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.RunUpdateFactOpportunitySavingsTask, Strata.CS.Jazz.Biz.CCI" + }, cancellationToken); + } + + return opportunity.InitiativeGuid; + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, opportunityGuid, initiative.InitiativeGUID); + return Guid.Empty; + } + } + + public async Task FinalizeChargeCodeInitiative(Guid initiativeGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var initiative = await dbContext.Initiatives.Where(i => i.InitiativeGUID.Equals(initiativeGuid)).SingleOrDefaultAsync(); + + try + { + initiative.CurrentStep = (int)InitiativeSteps.Tracking; + await dbContext.SaveChangesAsync(cancellationToken); + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, Guid.Empty, initiativeGuid); + } + } + + public async Task RunTracking(Guid initiativeGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var initiative = await dbContext.Initiatives.Where(i => i.InitiativeGUID.Equals(initiativeGuid)).SingleOrDefaultAsync(); + + try + { + EnqueueJobDto enqueueJobDto = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.Custom.ChargeCode.RunChargeCodeTrackingTask, Strata.CS.Jazz.Biz.CCI", + ContextData = JsonConvert.SerializeObject(new + { + configurationGuid = initiative.ConfigurationGUID, + initiativeGuid + }) + }; + var job = enqueueJobDto; + + var response = await _hangfireServiceClient.EnqueueJobAsync(job, cancellationToken); + return response.JobId; + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, Guid.Empty, initiativeGuid); + } + return Guid.Empty; + } + + public async Task UpdateSavings(Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunity = await dbContext.ChargeCodeOpportunities.SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunity == null) { return false; } + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(init => init.InitiativeGUID == opportunity.InitiativeGuid, cancellationToken); + + try + { + opportunity.LevelCases = await dbContext.ChargeCodeOpportunityLevelCases + .Where(cases => cases.OpportunityGuid == opportunity.OpportunityGuid && cases.IsBaseline).ToListAsync(); + var details = dbContext.ChargeCodeDetails.Where(detail => detail.OpportunityGUID == opportunityGuid && detail.IsBaseline).AsEnumerable(); + var identifiedSavings = _ccOpportunityDetailService.CalculateSavings(details); + + opportunity.IdentifiedSavings = identifiedSavings; + await dbContext.SaveChangesAsync(cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + + if (opportunity.InitiativeGuid == Guid.Empty) { return true; } // no initiative to update + + if (initiative == null) { return false; } + + _initiativeSavingsService.ApplySavingsToInitiative(opportunity, initiative, identifiedSavings); + + await dbContext.SaveChangesAsync(cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + return true; + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, opportunityGuid, initiative.InitiativeGUID); + return false; + } + } + } +} + diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Initiatives/ChargeCodeInitiativeDto.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Initiatives/ChargeCodeInitiativeDto.cs new file mode 100644 index 0000000..09f3173 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Initiatives/ChargeCodeInitiativeDto.cs @@ -0,0 +1,18 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives +{ + public class ChargeCodeInitiativeDto : InitiativeDto + { + public Guid ConfigurationGuid { get; set; } + public double Cases { get; set; } + public int EncounterGroup { get; set; } + public int OpportunityLevel { get; set; } + public List EncounterGroups { get; set; } + public List ChargeCodes { get; set; } + public List OpportunityLevels { get; set; } + public bool isSeasonal { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Initiatives/ICCInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Initiatives/ICCInitiativeService.cs new file mode 100644 index 0000000..49f9509 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Initiatives/ICCInitiativeService.cs @@ -0,0 +1,16 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives +{ + public interface ICCInitiativeService + { + Task CreateChargeCodeInitiative(Guid dbGuid, Initiative initiative, Guid opportunityGuid, CancellationToken cancellationToken); + Task UpdateChargeCodeInitiative(Guid dbGuid, Initiative initiative, Guid opportunityGuid, bool wasDetailsChanged, CancellationToken cancellationToken); + Task FinalizeChargeCodeInitiative(Guid initiativeGuid, CancellationToken cancellationToken); + Task RunTracking(Guid initiativeGuid, CancellationToken cancellationToken); + Task UpdateSavings(Guid opportunityGuid, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/CCInitiativeJob.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/CCInitiativeJob.cs new file mode 100644 index 0000000..9d0e9f7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/CCInitiativeJob.cs @@ -0,0 +1,156 @@ +using Hangfire; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.Hangfire.Filters; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Jobs +{ + public class CCInitiativeJob : ICCInitiativeJob + { + protected readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IHubContext _notificationHubContext; + private readonly ILogger _logger; + private readonly ICCOpportunityService _ccOpportunityService; + private readonly ICCInitiativeService _ccInitiativeService; + private readonly IClientNetworkOpportunityLinkService _clientNetworkOpportunityLinkService; + private readonly IStakeholderUser _stakeholderUser; + private readonly IBackgroundJobClient _backgroundJobClient; + + public CCInitiativeJob( + IAsyncDbContextFactory dbContextFactory, + IHubContext notificationHubContext, + ILogger logger, + ICCOpportunityService ccOpportunityService, + ICCInitiativeService ccInitiativeService, + IClientNetworkOpportunityLinkService clientNetworkOpportunityLinkService, + IStakeholderUser stakeholderUser, + IBackgroundJobClient backgroundJobClient) + { + _dbContextFactory = dbContextFactory; + _notificationHubContext = notificationHubContext; + _logger = logger; + _ccOpportunityService = ccOpportunityService; + _ccInitiativeService = ccInitiativeService; + _clientNetworkOpportunityLinkService = clientNetworkOpportunityLinkService; + _stakeholderUser = stakeholderUser; + _backgroundJobClient = backgroundJobClient; + } + + public async Task CreateChargeCodeInitiative(ChargeCodeInitiativeDto initiative, int? networkOpportunityId, Guid dbGuid, CancellationToken cancellationToken) + { + var jobId = _backgroundJobClient.Enqueue(job + => job.CreateChargeCodeInitiativeJob(initiative, networkOpportunityId, dbGuid, cancellationToken)); + return jobId; + } + + [DeleteOnSuccess] + [JobDisplayName("Create Charge Code Opportunity and Initiative on ({2})")] + [CaptureClaimsPrincipal] + public async Task CreateChargeCodeInitiativeJob(ChargeCodeInitiativeDto initiative, int? networkOpportunityId, Guid dbGuid, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(initiative.Name); + + // Save Opportunity + CCOpportunityDataModel opportunity = default; + if (initiative.OpportunityGuid != Guid.Empty) + { + opportunity = await _ccOpportunityService.GetById(initiative.OpportunityGuid, cancellationToken); + } + else + { + opportunity = initiative.MapToDataModel(); + var jobId = await _ccOpportunityService.CreateAsync(dbGuid, opportunity, cancellationToken); + + // Opportunity needs to finish first + await WaitForJob(jobId); + } + + // Save Initiative + var newInitiative = initiative.MapToInitiative(_stakeholderUser); + await _ccInitiativeService.CreateChargeCodeInitiative(dbGuid, newInitiative, opportunity.OpportunityGuid, cancellationToken); + + if (networkOpportunityId.HasValue) + { + await _clientNetworkOpportunityLinkService.AddOpportunityLinkAsync(dbGuid, networkOpportunityId.Value, opportunity.OpportunityGuid, cancellationToken); + } + await hub.SendInitiativeCreateSuccess(opportunity.OpportunityGuid, opportunity.Name); + } + + public async Task UpdateChargeCodeInitiative(ChargeCodeInitiativeDto initiative, int? networkOpportunityId, Guid dbGuid, CancellationToken cancellationToken) + { + var jobId = _backgroundJobClient.Enqueue(job + => job.UpdateChargeCodeInitiativeJob(initiative, networkOpportunityId, dbGuid, cancellationToken)); + return jobId; + } + + [DeleteOnSuccess] + [JobDisplayName("Update Charge Code Opportunity and Initiative on ({2})")] + [CaptureClaimsPrincipal] + public async Task UpdateChargeCodeInitiativeJob(ChargeCodeInitiativeDto initiativeOpportunity, int? networkOpportunityId, Guid dbGuid, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(initiativeOpportunity.Name); + + // Update Opportunity + var opportunity = initiativeOpportunity.MapToDataModel(); + var jobId = await _ccOpportunityService.UpdateAsync(dbGuid, opportunity, cancellationToken); + + var detailsChanged = jobId != Guid.Empty.ToString(); + // Opportunity needs to finish first + if (detailsChanged) + await WaitForJob(jobId); + + // Update Initiative + var initiative = initiativeOpportunity.MapToInitiative(_stakeholderUser); + var result = await _ccInitiativeService.UpdateChargeCodeInitiative(dbGuid, initiative, opportunity.OpportunityGuid, detailsChanged, cancellationToken); + if (!detailsChanged) + { + await hub.SendOpportunityCreateUpdateSuccess(opportunity.OpportunityGuid, true, opportunity.Name, true, OpportunityTypes.ChargeCode); + return; + } + + if (result == Guid.Empty) + { + await hub.SendOpportunityUpdateError(opportunity.Name, "Unable to update initiative."); + return; + } + + if (networkOpportunityId.HasValue) + { + await _clientNetworkOpportunityLinkService.AddOpportunityLinkAsync(dbGuid, networkOpportunityId.Value, opportunity.OpportunityGuid, cancellationToken); + } + await hub.SendOpportunityCreateUpdateSuccess(opportunity.OpportunityGuid, true, opportunity.Name, true, OpportunityTypes.ChargeCode); + } + + private async Task WaitForJob(string jobId) + { + var storageConnection = JobStorage.Current.GetConnection(); + var jobData = storageConnection.GetJobData(jobId); + var states = new List { "Enqueued", "Processing" }; + + while (states.Contains(jobData.State)) + { + await Task.Delay(500); + jobData = storageConnection.GetJobData(jobId); + } + if (jobData.State == "Deleted") { return false; } + + return true; + } + + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/CCOpportunityClientJob.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/CCOpportunityClientJob.cs new file mode 100644 index 0000000..e80faa0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/CCOpportunityClientJob.cs @@ -0,0 +1,81 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.FeatureFlags.Client; +using Strata.Hangfire.Filters; +using Strata.SqlTools.Configuration.Common.AsyncFactory; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Jobs +{ + public class CCOpportunityClientJob : ICCOpportunityClientJob + { + protected readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly ILogger _logger; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + + public CCOpportunityClientJob( + IAsyncDbContextFactory dbContextFactory, + IBackgroundJobClient backgroundJobClient, + ILogger logger, + IFeatureFlagServiceClient featureFlagServiceClient) + { + _dbContextFactory = dbContextFactory; + _backgroundJobClient = backgroundJobClient; + _logger = logger; + _featureFlagServiceClient = featureFlagServiceClient; + } + + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task RefreshClientOpportunityBaselineDetails(int strataId, Guid opportunityGuid, CancellationToken cancellationToken) + { + return await EnqueueOpportunityDetailJobAsync(strataId, opportunityGuid, true, cancellationToken); + } + + [CaptureClaimsPrincipal] + public async Task RefreshClientOpportunityTrackingDetails(int strataId, Guid opportunityGuid, CancellationToken cancellationToken) + { + return await EnqueueOpportunityDetailJobAsync(strataId, opportunityGuid, false, cancellationToken); + } + + private async Task EnqueueOpportunityDetailJobAsync(int strataId, Guid opportunityGuid, bool isBaseline, CancellationToken cancellationToken) + { + await using var dbContext = await GetDbContext(cancellationToken); + + var opportunity = await dbContext.ChargeCodeOpportunities + .FirstOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken) + ?? throw new InvalidOperationException($"Opportunity with Guid '{opportunityGuid}' not found."); + + if (isBaseline) + { + return _backgroundJobClient.Enqueue(job => + job.PopulateOpportunityBaselineDetailsAsync( + strataId, + opportunity.Name, + opportunity.OpportunityGuid, + cancellationToken)); + } + else + { + _ = await dbContext.Initiatives + .FirstOrDefaultAsync(i => i.InitiativeGUID == opportunity.InitiativeGuid, cancellationToken) + ?? throw new InvalidOperationException($"Initiative with Guid '{opportunity.InitiativeGuid}' not found for Opportunity '{opportunityGuid}'."); + + return _backgroundJobClient.Enqueue(job => + job.PopulateOpportunityTrackingDetailsAsync( + strataId, + opportunity.Name, + opportunity.OpportunityGuid, + cancellationToken)); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/CCOpportunityDetailJob.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/CCOpportunityDetailJob.cs new file mode 100644 index 0000000..5900d68 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/CCOpportunityDetailJob.cs @@ -0,0 +1,350 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Hangfire.Filters; +using Strata.Id.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Jobs +{ + public class CCOpportunityDetailJob + { + protected readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IFiscalMonthResolver _fiscalMonthResolver; + private readonly ICCOpportunityDetailService _ccOpportunityDetailService; + private readonly IInitiativeSavingsService _initiativeSavingsService; + private readonly IHubContext _notificationHubContext; + private readonly IIdServiceClient _idServiceClient; + private readonly ILogger _logger; + private readonly IConfigurationService _configurationService; + private readonly ICCOpportunityLevelCaseService _ccopportunityLevelCaseService; + + public CCOpportunityDetailJob( + IAsyncDbContextFactory dbContextFactory, + IFiscalMonthResolver fiscalMonthResolver, + ICCOpportunityDetailService ccOpportunityDetailService, + IInitiativeSavingsService initiativeSavingsService, + IHubContext notificationHubContext, + IIdServiceClient idServiceClient, + ILogger logger, + IConfigurationService configurationService, + ICCOpportunityLevelCaseService ccopportunityLevelCaseService) + { + _dbContextFactory = dbContextFactory; + _fiscalMonthResolver = fiscalMonthResolver; + _ccOpportunityDetailService = ccOpportunityDetailService; + _initiativeSavingsService = initiativeSavingsService; + _notificationHubContext = notificationHubContext; + _idServiceClient = idServiceClient; + _logger = logger; + _configurationService = configurationService; + _ccopportunityLevelCaseService = ccopportunityLevelCaseService; + } + + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + [DeleteOnSuccess] + [JobDisplayName("Populate Charge Code details for {1} on ({0})")] + [CaptureClaimsPrincipal] + public async Task PopulateOpportunityDetailsAsync(int strataId, string name, Guid opportunityGuid, bool isBaseline, bool isUpdate, bool willRedirect, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + dbContext.Database.SetCommandTimeout(TimeSpan.FromMinutes(30)); + + try + { + var hub = _notificationHubContext.Clients.Group(name); + if (isUpdate) + { + await hub.SendOpportunityUpdateDetails(name, 0); + } + else + { + await hub.SendOpportunityCreateDetails(name, 0); + } + + var opportunity = await dbContext.ChargeCodeOpportunities + .SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid); + if (opportunity == null) + { + throw new ArgumentException($"Opportunity Not Found for opportunty guid {opportunityGuid}", nameof(opportunityGuid)); + } + var configuration = opportunity.Configuration + ?? await dbContext.Configurations.SingleOrDefaultAsync(c => c.ConfigurationGuid == opportunity.ConfigurationGuid, cancellationToken); + if (opportunity.Configuration == null) + { + opportunity.Configuration = configuration; + } + opportunity.Configuration.FiscalMonthResolver = _fiscalMonthResolver; + var opportunityLevel = Enum.TryParse(opportunity.OpportunityLevel, out var level) + ? level : OpportunityLevel.Organization; + + var detailResults = new List(); + List queryResults = default; + List detailRows = default; + IEnumerable detailQuery = default; + + try + { + var databaseGuid = await _idServiceClient.GetDatabaseGUID(strataId, cancellationToken); + queryResults = await _ccOpportunityDetailService.GetCCOpportunityDetailQueryResult(opportunity, configuration, cancellationToken, isBaseline); + } + catch (Exception ex) + { + _logger.LogContextError(ex, dbContext, nameof(_ccOpportunityDetailService.GetCCOpportunityDetailQueryResult), + new Dictionary { + { nameof(opportunityGuid), opportunityGuid }, + { nameof(isBaseline), isBaseline } + }); + } + + try + { + detailRows = await dbContext.ChargeCodeDetails + .Where(cc => cc.OpportunityGUID == opportunityGuid && cc.IsBaseline == isBaseline) + .ToListAsync(); + } + catch (Exception ex) + { + _logger.LogContextError(ex, dbContext, nameof(dbContext.ChargeCodeDetails), + new Dictionary { + { nameof(opportunityGuid), opportunityGuid }, + { nameof(isBaseline), isBaseline } + }); + } + + try + { + if (!queryResults.IsEmpty() && !detailRows.IsEmpty()) + { + detailQuery = from item in queryResults + join row in detailRows + on new { item.OpportunityLevelId, item.ChargeCodeId, item.Month, item.IsBaseline } + equals new { row.OpportunityLevelId, row.ChargeCodeId, row.Month, row.IsBaseline } + into itemRow + select new DetailResults(item, itemRow.DefaultIfEmpty().FirstOrDefault()); + } + else if (!queryResults.IsEmpty()) + { + detailQuery = queryResults.Select(item => new DetailResults(item, null)); + } + + if (!detailQuery.IsEmpty()) + { + detailQuery.Batch(1000) + .ForEach(batch => detailResults.AddRange(batch)); + } + } + catch (Exception ex) + { + _logger.LogQueryError(ex, dbContext, nameof(dbContext.ChargeCodeDetails), + $"Where(cc => cc.OpportunityGUID == '{opportunityGuid}' && cc.IsBaseline == {isBaseline})"); + } + + var chargeCodesMembers = opportunity.ChargeCodeSelections.Where(cc => cc.Id != null); + var chargeCodeNameLookup = chargeCodesMembers.ToDictionary(key => key.Id, value => value.Name); + var opportunityLevelMembers = opportunity.OpportunityLevelSelections; + var opportunityLevelNameLookup = opportunityLevel is not OpportunityLevel.Organization ? opportunityLevelMembers.ToDictionary(key => key.Id, value => value.Name) + : new Dictionary() { { "0", "System" } }; + + var batches = detailResults + .GroupBy(result => new { result.Item.OpportunityLevelId, result.Item.ChargeCodeId }, + (Key, chargeCodeDetailResults) => new { Key, chargeCodeDetailResults }) + .Batch(20); + var batchCount = batches.Count(); + var progress = 0; + foreach (var batch in batches) + { + if (isUpdate) + { + await hub.SendOpportunityUpdateDetails(name, (++progress).DivideBy(batchCount)); + } + else + { + await hub.SendOpportunityCreateDetails(name, (++progress).DivideBy(batchCount)); + } + + var chargeCodeOpportunityDetails = new List(); + foreach (var result in batch) + { + foreach (var detail in result.chargeCodeDetailResults) + { + if (detail.Row != null) + { + var detailRow = detail.Row; + detailRow.TotalUnitsOfService = detail.Item.TotalUnitsOfService; + detailRow.TotalCost = configuration.IsUsingIndirectCost ? detail.Item.VariableIndirectCost + detail.Item.VariableDirectCost : detail.Item.VariableDirectCost; + detailRow.TotalCases = detail.Item.TotalCases; + } + else if (!chargeCodeNameLookup.ContainsKey(detail.Item.ChargeCodeId.ToString()) + || !opportunityLevelNameLookup.ContainsKey(detail.Item.OpportunityLevelId.ToString())) + { + _logger.LogContextWarning(dbContext, "chargeCodeNameLookup or opportunityLevelNameLookup failed", + new Dictionary + { + { nameof(detail.Item.ChargeCodeId), detail.Item.ChargeCodeId.ToString() }, + { nameof(detail.Item.OpportunityLevelId), detail.Item.OpportunityLevelId.ToString() } + }); + } + else + { + var chargeCodeOpportunityDetail = detail.Item.MapFromQueryResult(); + chargeCodeOpportunityDetail.TotalCost = configuration.IsUsingIndirectCost ? detail.Item.VariableIndirectCost + detail.Item.VariableDirectCost : detail.Item.VariableDirectCost; + chargeCodeOpportunityDetail.OpportunityGUID = opportunity.OpportunityGuid; + chargeCodeOpportunityDetail.ChargeCodeName = chargeCodeNameLookup[detail.Item.ChargeCodeId.ToString()]; + chargeCodeOpportunityDetail.OpportunityLevelName = opportunityLevelNameLookup[detail.Item.OpportunityLevelId.ToString()]; + chargeCodeOpportunityDetail.GoalTotalUnitsOfService = chargeCodeOpportunityDetail.TotalUnitsOfService; + chargeCodeOpportunityDetails.Add(chargeCodeOpportunityDetail); + } + } + } + + if (chargeCodeOpportunityDetails.Any()) + { + var chargeCodeGroups = chargeCodeOpportunityDetails + .Where(detail => detail.IsBaseline.Equals(isBaseline)) + .GroupBy(detail => new { detail.OpportunityLevelId, detail.ChargeCodeId }) + .Select(group => new + { + group.Key, + ChargeCodeDetails = group.OrderBy(detail => detail.ChargeCodeOpportunityDetailId) + }); + + foreach (var chargeCodeGroup in chargeCodeGroups) + { + var totalCostSum = chargeCodeGroup.ChargeCodeDetails.Sum(detail => detail.TotalCost); + var totalUnitsOfServiceSum = chargeCodeGroup.ChargeCodeDetails.Sum(detail => detail.TotalUnitsOfService); + chargeCodeGroup.ChargeCodeDetails.ForEach(detail => detail.GoalCostPerUnit = (totalCostSum.DivideBy(totalUnitsOfServiceSum))); + dbContext.ChargeCodeDetails.AddRange(chargeCodeGroup.ChargeCodeDetails.ToList()); + } + } + await dbContext.SaveChangesAsync(cancellationToken); + } + + if (isBaseline) + { + var details = dbContext.ChargeCodeDetails.Where(detail => detail.OpportunityGUID == opportunityGuid && detail.IsBaseline).AsEnumerable(); + var identifiedSavings = _ccOpportunityDetailService.CalculateSavings(details); + opportunity.IdentifiedSavings = identifiedSavings; + + var databaseGuid = await _idServiceClient.GetDatabaseGUID(strataId, cancellationToken); + var levelCases = await PopulateBaselineLevelCases(opportunity, cancellationToken); + opportunity.Cases = levelCases.Where(x => x.IsBaseline).Sum(x => x.Cases); + await _ccopportunityLevelCaseService.AddOrUpdateLevelCases(levelCases, cancellationToken); + } + + if (opportunity.InitiativeGuid != Guid.Empty) + { + opportunity.LevelCases = await dbContext.ChargeCodeOpportunityLevelCases + .Where(cases => cases.OpportunityGuid == opportunity.OpportunityGuid && cases.IsBaseline).ToListAsync(); + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(i => i.InitiativeGUID == opportunity.InitiativeGuid); + _initiativeSavingsService.ApplySavingsToInitiative(opportunity, initiative, opportunity.IdentifiedSavings); + } + await dbContext.SaveChangesAsync(); + if (willRedirect) + await hub.SendOpportunityCreateUpdateSuccess(opportunity.OpportunityGuid, true, opportunity.Name, true, OpportunityTypes.ChargeCode); + } + catch (Exception ex) + { + var db = (await _idServiceClient.GetStrataIds()).SingleOrDefault(i => i.StrataId == strataId); + if (db == null) + { + _logger.LogContextError(ex, dbContext, nameof(PopulateOpportunityDetailsAsync), + new Dictionary + { + { nameof(strataId), strataId }, + { nameof(name), name }, + { nameof(opportunityGuid), opportunityGuid }, + { nameof(isBaseline), isBaseline }, + { nameof(isUpdate), isUpdate } + }); + } + else + { + _logger.LogContextError(ex, db, nameof(PopulateOpportunityDetailsAsync), + new Dictionary + { + { nameof(strataId), strataId }, + { nameof(name), name }, + { nameof(opportunityGuid), opportunityGuid }, + { nameof(isBaseline), isBaseline }, + { nameof(isUpdate), isUpdate } + }); + } + await _notificationHubContext.Clients.Group(name).SendOpportunityCreateError(name); + } + } + + internal class DetailResults + { + public CCOpportunityDetailQueryResult Item { get; set; } + public ChargeCodeOpportunityDetail? Row { get; set; } + public DetailResults(CCOpportunityDetailQueryResult item, ChargeCodeOpportunityDetail? row) + { + Item = item; + Row = row; + } + } + + private async Task> PopulateBaselineLevelCases(CCOpportunityDataModel opportunity, CancellationToken cancellationToken) + { + var configuration = await _configurationService.GetConfigurationAsync(opportunity.ConfigurationGuid, cancellationToken); + var levelCaseQueryResult = await _ccOpportunityDetailService.CCOpportunityLevelCasesQuery(opportunity, configuration, cancellationToken); + + List levelCases = new List(); + foreach (var result in levelCaseQueryResult) + { + var levelCase = new ChargeCodeOpportunityLevelCase + { + OpportunityGuid = opportunity.OpportunityGuid, + IsBaseline = true, + Month = result.Month, + Cases = result.TotalCases, + OpportunityLevelId = result.OpportunityLevelId + }; + levelCases.Add(levelCase); + } + + return levelCases; + } + + [DeleteOnSuccess] + [JobDisplayName("Populate Charge Code Baseline Details for {1} on ({0})")] + [Semaphore(nameof(CCOpportunityDetailJob.PopulateOpportunityBaselineDetailsAsync))] + [CaptureClaimsPrincipal] + public async Task PopulateOpportunityBaselineDetailsAsync(int strataId, string name, Guid opportunityGuid, CancellationToken cancellationToken) + { + await PopulateOpportunityDetailsAsync(strataId, name, opportunityGuid, isBaseline: true, isUpdate: false, willRedirect: false, cancellationToken); + } + + [DeleteOnSuccess] + [JobDisplayName("Populate Charge Code Tracking Details for {1} on ({0})")] + [Semaphore(nameof(CCOpportunityDetailJob.PopulateOpportunityTrackingDetailsAsync))] + [CaptureClaimsPrincipal] + public async Task PopulateOpportunityTrackingDetailsAsync(int strataId, string name, Guid opportunityGuid, CancellationToken cancellationToken) + { + await PopulateOpportunityDetailsAsync(strataId, name, opportunityGuid, isBaseline: false, isUpdate: false, willRedirect: false, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/ICCInitiativeJob.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/ICCInitiativeJob.cs new file mode 100644 index 0000000..46d6078 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/ICCInitiativeJob.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Jobs +{ + public interface ICCInitiativeJob + { + Task CreateChargeCodeInitiative(ChargeCodeInitiativeDto initiative, int? networkOpportunityId, Guid dbGuid, CancellationToken cancellationToken); + + Task UpdateChargeCodeInitiative(ChargeCodeInitiativeDto initiative, int? networkOpportunityId, Guid dbGuid, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/ICCOpportunityClientJob.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/ICCOpportunityClientJob.cs new file mode 100644 index 0000000..b4195a6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Jobs/ICCOpportunityClientJob.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Biz.DbContexts; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Jobs +{ + public interface ICCOpportunityClientJob + { + Task GetDbContext(CancellationToken cancellationToken); + Task RefreshClientOpportunityBaselineDetails(int strataId, Guid opportunityGuid, CancellationToken cancellationToken); + Task RefreshClientOpportunityTrackingDetails(int strataId, Guid opportunityGuid, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunity.cs new file mode 100644 index 0000000..25bd5e4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunity.cs @@ -0,0 +1,18 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + [Table("viewChargeCodeOpportunity", Schema = "cci")] + public class CCOpportunity : Opportunity + { + public string Name { get; set; } + public double Cases { get; set; } + public ChargeCodeGroup ChargeCodeGroup { get; set; } + public string ChargeCodeGroupValue { get; set; } + public string OpportunityLevel { get; set; } + public bool IsSeasonal { get; set; } + public long OpportunityId { get; set; } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDataModel.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDataModel.cs new file mode 100644 index 0000000..f1117a1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDataModel.cs @@ -0,0 +1,129 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Schema.Client.Dtos; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + [Table("ChargeCodeOpportunity", Schema = "cci")] + public class CCOpportunityDataModel : Opportunity, IOpportunity + { + public CCOpportunityDataModel() + { + OpportunityGuid = Guid.NewGuid(); + DateCreated = DateTime.Now; + ChargeCodeJson = JsonSerializer.Serialize(new List()); + ClinicalIndicatorJson = JsonSerializer.Serialize(new object()); + CaseTypeJson = JsonSerializer.Serialize(new object()); + OpportunityLevelJson = JsonSerializer.Serialize(new List()); + } + + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long OpportunityId { get; set; } + public string Name { get; set; } + public bool IsSeasonal { get; set; } + public string ChargeCodeJson { get; set; } + public string OpportunityLevel { get; set; } + public string OpportunityLevelJson { get; set; } + public int Cases { get; set; } + public DateTime DateCreated { get; set; } + + [NotMapped] + public int EncounterGroup { get; set; } + public string ClinicalIndicatorJson { get; set; } + public string CaseTypeJson { get; set; } + + [NotMapped] + public IEnumerable Details { get; set; } + [NotMapped] + public IEnumerable LevelCases { get; set; } + + // For persisting DTO selections from UI + [NotMapped] + public List PatientPopulations { get; set; } + [NotMapped] + public List CaseTypes { get; set; } + [NotMapped] + public List ChargeCodes { get; set; } + [NotMapped] + public List OpportunityLevels { get; set; } + + [NotMapped] + public OpportunityLevel OpportunityLevelEnum + => Enum.TryParse(OpportunityLevel, out var result) + ? result + : Shared.OpportunityLevel.Organization; + [NotMapped] + public IEnumerable OpportunityLevelSelections + => OpportunityLevelJson.Selections(); + [NotMapped] + public IEnumerable ChargeCodeSelections + => ChargeCodeJson.Selections(); + [NotMapped] + public IEnumerable ClinicalIndicatorSelections + => ClinicalIndicatorJson.Selections(); + [NotMapped] + public IEnumerable CaseTypeSelections + => CaseTypeJson.Selections(); + + [NotMapped] + public virtual Configuration.Configuration Configuration { get; set; } + + // IOpportunity properties needed to implement interface + [NotMapped] + public string OpportunityKey + { + get + { + return $"CC - {OpportunityId}"; + } + set { } + } + [NotMapped] + public new bool HasInitiative { get; set; } + [NotMapped] + public new string Initiative { get; set; } + + public override void Update(IOpportunity updates) + { + base.Update(updates); + var ccUpdates = updates as CCOpportunityDataModel; + if (ccUpdates == null) return; + Name = ccUpdates.Name; + } + + public bool IsDetailsShouldBeUpdated(CCOpportunityDataModel originalObject) + { + return originalObject.ClinicalIndicatorJson != ClinicalIndicatorJson + || originalObject.ChargeCodeJson != ChargeCodeJson + || originalObject.IsSeasonal != IsSeasonal + || originalObject.OpportunityLevel != OpportunityLevel + || originalObject.OpportunityLevelJson != OpportunityLevelJson + || originalObject.CaseTypeJson != CaseTypeJson; + } + } + + internal class HierarchyPathsAndMembers + { + public List HierarchyPaths { get; set; } + public List Members { get; set; } + + public HierarchyPathsAndMembers() + { + HierarchyPaths = new List(); + Members = new List(); + } + } + + public enum ChargeCodeGroup + { + Both = -1, + CaseType = 0, + PatientPopulation = 1 + } + +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDataModelMapper.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDataModelMapper.cs new file mode 100644 index 0000000..d3a2894 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDataModelMapper.cs @@ -0,0 +1,113 @@ +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class CCOpportunityDataModelMapper + { + public static CCOpportunityDataModel MapToDataModel(this ChargeCodeInitiativeDto src) + { + var mapped = Mapped(src); + if (mapped == null) { return default; } + mapped.OpportunityLevel = Enum.GetName(typeof(OpportunityLevel), src.OpportunityLevel); + mapped.IsSeasonal = src.isSeasonal; + if (src.EncounterGroup == (int)EncounterGroup.CaseType) + { + mapped.CaseTypes = src.EncounterGroups; + } + else + { + mapped.PatientPopulations = src.EncounterGroups; + } + return mapped; + } + + public static CCOpportunityDataModel MapToDataModel(this CCOpportunityDto src) + { + var mapped = Mapped(src); + if (mapped == null) { return default; } + mapped.OpportunityLevel = Enum.GetName(typeof(OpportunityLevel), src.OpportunityLevel); + if (src.EncounterGroup == (int)EncounterGroup.CaseType) + { + mapped.CaseTypes = src.EncounterGroups; + } + else + { + mapped.PatientPopulations = src.EncounterGroups; + } + return mapped; + } + + public static CCOpportunityDto MapToDto(this CCOpportunityDataModel src) + { + var mapped = Mapped(src); + if (mapped == null) { return default; } + mapped.EncounterGroups = src.EncounterGroup == (int)EncounterGroup.CaseType + ? src.CaseTypeJson.GetNodeNames().ToList() + : src.ClinicalIndicatorJson.GetNodeNames().ToList(); + mapped.ChargeCodes = src.ChargeCodeJson.GetNodeNames().ToList(); + mapped.OpportunityLevels = src.OpportunityLevelJson.GetNodeNames().ToList(); + mapped.BaselineStartDate = src.Configuration.GetBaselineStartDate(Generics.OpportunityTypes.OrgDefined); + mapped.BaselineEndDate = src.Configuration.GetBaselineEndDate(Generics.OpportunityTypes.OrgDefined); + mapped.TrackingStartDate = src.Configuration.GetTrackingStartDate(Generics.OpportunityTypes.OrgDefined); + mapped.TrackingEndDate = src.Configuration.GetTrackingEndDate(); + return mapped; + } + + public static CCOpportunitySaveModel MapToSaveModel(this CCOpportunityDataModel src) + { + var mapped = MappedToSaveModel(src); + if (mapped == null) { return default; } + mapped.OpportunityLevel = (int)Enum.Parse(src.OpportunityLevel, true); + mapped.EncounterGroups = src.EncounterGroup == (int)EncounterGroup.CaseType + ? src.CaseTypeJson.HierarchyPaths().ToList() + : src.ClinicalIndicatorJson.HierarchyPaths().ToList(); + mapped.ChargeCodes = src.ChargeCodeJson.HierarchyPaths().ToList(); + mapped.OpportunityLevels = mapped.OpportunityLevel == (int)OpportunityLevel.Organization + ? new List { "System" } + : src.OpportunityLevelJson.HierarchyPaths().ToList(); + return mapped; + } + + public static CCOpportunityDataModel MapToDataModel(this CCOpportunitySaveModel src) + { + var mapped = Mapped(src); + if (mapped == null) { return default; } + mapped.OpportunityLevel = Enum.GetName(typeof(OpportunityLevel), src.OpportunityLevel); + if (src.EncounterGroup == (int)EncounterGroup.CaseType) + { + mapped.CaseTypes = src.EncounterGroups.ToList(); + } + else + { + mapped.PatientPopulations = src.EncounterGroups.ToList(); + } + return mapped; + } + + public static partial ChargeCodeOpportunityDetail MapFromQueryResult(this CCOpportunityDetailQueryResult src); + + [MapProperty(nameof(CCOpportunityDataModel.Cases), nameof(CCOpportunityDto.TotalCases))] + [MapperIgnoreTarget(nameof(CCOpportunityDto.OpportunityLevel))] + private static partial CCOpportunityDto Mapped(CCOpportunityDataModel src); + + [MapperIgnoreSource(nameof(CCOpportunityDataModel.OpportunityLevel))] + private static partial CCOpportunitySaveModel MappedToSaveModel(CCOpportunityDataModel src); + + private static partial CCOpportunityDataModel Mapped(ChargeCodeInitiativeDto src); + + private static partial CCOpportunityDataModel Mapped(CCOpportunitySaveModel src); + + [MapProperty(nameof(CCOpportunityDto.EncounterGroup), nameof(CCOpportunityDataModel.EncounterGroup))] + [MapperIgnoreSource(nameof(CCOpportunityDto.OpportunityLevel))] + private static partial CCOpportunityDataModel Mapped(CCOpportunityDto src); + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetail.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetail.cs new file mode 100644 index 0000000..1545166 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetail.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + [Table("ChargeCodeOpportunityDetail", Schema = "cci")] + public class ChargeCodeOpportunityDetail + { + public int ChargeCodeOpportunityDetailId { get; set; } + public Guid OpportunityGUID { get; set; } + public int OpportunityLevelId { get; set; } + public string OpportunityLevelName { get; set; } + public int ChargeCodeId { get; set; } + public string ChargeCodeName { get; set; } + public int TotalCases { get; set; } + public double TotalUnitsOfService { get; set; } + public double TotalCost { get; set; } + public string ChargeCodeRollupName { get; set; } + public double GoalTotalUnitsOfService { get; set; } + public bool IsBaseline { get; set; } + public int Month { get; set; } + public double GoalCostPerUnit { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailQueryResult.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailQueryResult.cs new file mode 100644 index 0000000..0b1d1c0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailQueryResult.cs @@ -0,0 +1,17 @@ +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class CCOpportunityDetailQueryResult + { + public int ChargeCodeId { get; set; } + public string ChargeCodeName { get; set; } + public string ChargeCodeRollupName { get; set; } + public int OpportunityLevelId { get; set; } + public string OpportunityLevelName { get; set; } + public int TotalCases { get; set; } + public double TotalUnitsOfService { get; set; } + public double VariableDirectCost { get; set; } + public double VariableIndirectCost { get; set; } + public bool IsBaseline { get; set; } + public int Month { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailRowData.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailRowData.cs new file mode 100644 index 0000000..4eb3eec --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailRowData.cs @@ -0,0 +1,38 @@ +using Strata.ContinuousImprovement.Biz.Utilities; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class CCOpportunityDetailRowData + { + public string Name { get; set; } + public int TotalCases { get; set; } + public double BaselineUnitsOfServicePerCase { get; set; } + public double BaselineTotalUnitsOfService { get; set; } + public double BaselineCostPerUnit { get; set; } + public double BaselineCostPerCase { get; set; } + public double BaselineTotalCost { get; set; } + public double GoalUnitsOfServicePerCase { get; set; } + public double GoalTotalUnitsOfService { get; set; } + public double GoalCostPerUnit { get; set; } + public double GoalCostPerCase { get; set; } + public double GoalTotalCost { get; set; } + public double Improvement { get; set; } + + public CCOpportunityDetailRowData(string name, int totalCases, double totalCost, double totalUnitsOfService, double goalTotalUnitsOfService, double goalCostPerUnit) + { + Name = name; + TotalCases = totalCases; + BaselineTotalCost = totalCost; + BaselineTotalUnitsOfService = totalUnitsOfService; + BaselineUnitsOfServicePerCase = totalUnitsOfService.DivideBy(totalCases); + BaselineCostPerUnit = totalCost.DivideBy(totalUnitsOfService); + BaselineCostPerCase = totalCost.DivideBy(totalCases); + GoalTotalUnitsOfService = goalTotalUnitsOfService; + GoalTotalCost = goalCostPerUnit * goalTotalUnitsOfService; + GoalCostPerUnit = goalCostPerUnit; + GoalUnitsOfServicePerCase = GoalTotalUnitsOfService.DivideBy(totalCases); + GoalCostPerCase = GoalTotalCost.DivideBy(totalCases); + Improvement = BaselineTotalCost - GoalTotalCost; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailService.cs new file mode 100644 index 0000000..175cf5a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailService.cs @@ -0,0 +1,332 @@ +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using Strata.Schema.Client; +using Strata.Schema.Client.Dtos; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class CCOpportunityDetailService : ICCOpportunityDetailService + { + private readonly ISchemaServiceClient _schemaServiceClient; + private readonly ISnowflakeDatabaseContext _snowflakeDbContext; + protected readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ILogger _logging; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + private readonly ClaimsPrincipal _user; + + public CCOpportunityDetailService(IAsyncDbContextFactory dbContextFactory, + ISchemaServiceClient schemaServiceClient, + ISnowflakeDatabaseContext snowflakeDatabaseContext, + IFeatureFlagServiceClient featureFlagServiceClient, + ILogger logging, + IClaimsPrincipalAccessor claimsPrincipalAccessor) + { + _dbContextFactory = dbContextFactory; + _schemaServiceClient = schemaServiceClient; + _snowflakeDbContext = snowflakeDatabaseContext; + _featureFlagServiceClient = featureFlagServiceClient; + _logging = logging; + _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + } + + + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public double CalculateSavings(IEnumerable details) + { + double identifiedSavings = 0; + + var detailsGroup = details.GroupBy(d => new { d.OpportunityLevelId, d.ChargeCodeId }); + foreach (var groupedOpportunityDetails in detailsGroup) + { + var totalCost = groupedOpportunityDetails.Sum(data => data.TotalCost); + var goalTotalCost = groupedOpportunityDetails.Sum(data => data.GoalTotalUnitsOfService * data.GoalCostPerUnit); + + identifiedSavings += totalCost - goalTotalCost; + } + + return identifiedSavings; + } + + public async Task> GetCCOpportunityDetailQueryResult(CCOpportunityDataModel opportunity, + Configuration.Configuration configuration, CancellationToken cancellationToken, bool isBaseline = true) + { + var results = new List(); + var sqlParams = GetSQLParameters(opportunity, configuration, isBaseline); + var isUsingSnowflake = await IsUsingSnowflake(cancellationToken); + + if (!sqlParams.Exists(p => p.ParameterName.StartsWith("@indicatorId")) + && !sqlParams.Exists(p => p.ParameterName.StartsWith("@caseTypeId"))) + { + _logging.LogServiceInformation("No Case Types or Patient Populations selected"); + return results; + } + else if (sqlParams.Count > 2099) + { + _logging.LogServiceInformation($"Too many parameters ({sqlParams.Count}) supplied for sql query"); + return results; + } + + var encounterGroupQueryHelper = new EncounterGroupQueryHelper(sqlParams); + if (string.IsNullOrEmpty(encounterGroupQueryHelper.WhereClause)) + { + _logging.LogServiceInformation("No Case Types or Patient Populations selected"); + return results; + } + + if (opportunity.OpportunityLevelEnum is not OpportunityLevel.Organization) + { + // Batch query opportunity level selections + foreach (var oppLevelIdBatch in opportunity.OpportunityLevelSelections.Batch(500)) + { + var oppBatchParams = new List(); + oppBatchParams.AddRange(sqlParams.Where(p => !p.ParameterName.StartsWith("@oppLevelId", StringComparison.CurrentCultureIgnoreCase))); + oppBatchParams.Add(new SqlParameter("@oppLevelIds", oppLevelIdBatch.GetIdsForMemberDtos())); + + var serviceLineDimensionName = await GetScoreDimensionFriendlyName(configuration.ServiceLineDimensionGuid); + var serviceLineColumnName = await GetScoreAttributeFriendlyName(configuration.ServiceLineDimensionGuid, cancellationToken); + + var opportunityLevelQueryHelper = new OpportunityLevelQueryHelper(opportunity.OpportunityLevelEnum, configuration, serviceLineDimensionName, serviceLineColumnName); + + var query = new OpportunityDetailQuery(opportunity.OpportunityLevelEnum, encounterGroupQueryHelper, opportunityLevelQueryHelper, isUsingSnowflake).GetQuery(); + var queryResult = await PerformDetailQuery(query, oppBatchParams, cancellationToken); + results.AddRange(queryResult); + } + } + else + { + var query = new OpportunityDetailQuery(opportunity.OpportunityLevelEnum, encounterGroupQueryHelper, default, isUsingSnowflake).GetQuery(); + var queryResult = await PerformDetailQuery(query, sqlParams, cancellationToken); + results.AddRange(queryResult); + } + var allDetailRecords = opportunity.OpportunityLevelEnum == OpportunityLevel.Organization + ? await CreateDetailsForAllChargeCodes(opportunity.ChargeCodeSelections, isBaseline, cancellationToken) + : await CreateDetailsForAllChargeCodes(opportunity.ChargeCodeSelections, opportunity.OpportunityLevelSelections, isBaseline, cancellationToken); + if (!results.Any()) { return allDetailRecords.ToList(); } + + // Add any charge code detail records missing from query dataset + var missingChargeCodeDetails = allDetailRecords + .Where(record => !results + .Exists(result => record.ChargeCodeId == result.ChargeCodeId + && record.OpportunityLevelId == result.OpportunityLevelId + && record.Month == result.Month + && record.IsBaseline == result.IsBaseline)); + results.AddRange(missingChargeCodeDetails); + + return results.ToList(); + } + + /// + /// Creates a LINQ query that calculates the opportunity level cases (unique encounters) + /// + /// + /// The current fiscal year configuration + /// + ///A LINQ query of detail records for further processing + public async Task> CCOpportunityLevelCasesQuery(CCOpportunityDataModel opportunity, Configuration.Configuration configuration, CancellationToken cancellationToken, bool isBaseline = true) + { + var sqlParams = GetSQLParameters(opportunity, configuration, isBaseline); + + var results = new List(); + var encounterQueryHelper = new EncounterGroupQueryHelper(sqlParams); + + if (opportunity.OpportunityLevelEnum is not OpportunityLevel.Organization) + { + var serviceLineDimensionName = await GetScoreDimensionFriendlyName(configuration.ServiceLineDimensionGuid); + var serviceLineColumnName = await GetScoreAttributeFriendlyName(configuration.ServiceLineDimensionGuid, cancellationToken); + + var opportunityLevelQueryHelper = new OpportunityLevelQueryHelper(opportunity.OpportunityLevelEnum, configuration, serviceLineDimensionName, serviceLineColumnName); + + var query = new OpportunityDetailLevelQuery(opportunity.OpportunityLevelEnum, encounterQueryHelper, opportunityLevelQueryHelper).GetQuery(); + return await PerformLevelCasesQuery(query, sqlParams, cancellationToken); + } + else + { + var query = new OpportunityDetailLevelQuery(opportunity.OpportunityLevelEnum, encounterQueryHelper, default).GetQuery(); + return await PerformLevelCasesQuery(query, sqlParams, cancellationToken); + } + } + + private async Task> PerformLevelCasesQuery(string query, IEnumerable sqlParameters, CancellationToken cancellationToken) + { + var snowflakeDbContext = await IsUsingSnowflake(cancellationToken) ? _snowflakeDbContext : null; + await using var jazzDbContext = await GetDbContext(cancellationToken); + var result = await query.PerformQuery(sqlParameters, snowflakeDbContext, jazzDbContext, _logging, cancellationToken); + return result.ToList(); + } + + private async Task> PerformDetailQuery(string query, IEnumerable sqlParameters, CancellationToken cancellationToken) + { + var snowflakeDbContext = await IsUsingSnowflake(cancellationToken) ? _snowflakeDbContext : null; + await using var jazzDbContext = await GetDbContext(cancellationToken); + var result = await query.PerformQuery(sqlParameters, snowflakeDbContext, jazzDbContext, _logging, cancellationToken); + return result.ToList(); + } + + private string InterpolateQueryArrayValues(string query, ref IEnumerable sqlParameters) + { + foreach (var parameterName in new[] { "@oppLevelIds", "@caseTypeIds", "@chargeCodeIds", "@indicatorIds" }) + { + var parameter = sqlParameters.SingleOrDefault(p => p.ParameterName.Equals(parameterName, StringComparison.CurrentCultureIgnoreCase)); + if (parameter == null) { continue; } + if (!query.Contains(parameterName, StringComparison.CurrentCultureIgnoreCase)) + { + sqlParameters = sqlParameters.Where(p => !p.ParameterName.Equals(parameterName, StringComparison.CurrentCultureIgnoreCase)).ToList(); + } + else + { + var values = (IEnumerable)parameter.Value; + parameter.Value = string.Join(",", values); + query = query.Replace(parameter.ParameterName, parameter.Value.ToString(), StringComparison.CurrentCultureIgnoreCase); + } + }; + return query; + } + + private async Task> CreateDetailsForAllChargeCodes(IEnumerable chargeCodeMembers, bool isBaseline, CancellationToken cancellationToken) + { + Func IdParse = Id => Int32.TryParse(Id, out var id) ? id : 0; + var monthList = new List() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + await using var jazzDbContext = GetDbContext(cancellationToken).Result; + try + { + return (from chargeCodeMember in chargeCodeMembers + from month in monthList + join chargeCode in jazzDbContext.ChargeCodes on IdParse(chargeCodeMember.Id) equals chargeCode.ChargeCodeId + select new CCOpportunityDetailQueryResult + { + ChargeCodeId = IdParse(chargeCodeMember.Id), + ChargeCodeName = chargeCodeMember.Name, + OpportunityLevelId = 0, + ChargeCodeRollupName = chargeCode.Rollup, + IsBaseline = isBaseline, + Month = month + }).ToList(); + } + catch (Exception ex) + { + + _logging.LogContextError(ex, jazzDbContext, nameof(CreateDetailsForAllChargeCodes), + new Dictionary + { + { nameof(chargeCodeMembers), chargeCodeMembers.ParamDictionary() }, + { nameof(isBaseline), isBaseline } + }); + } + return Enumerable.Empty(); + } + + private async Task> CreateDetailsForAllChargeCodes(IEnumerable chargeCodeMembers, IEnumerable opportunityLevelMembers, bool isBaseline, CancellationToken cancellationToken) + { + Func IdParse = Id => Int32.TryParse(Id, out var id) ? id : 0; + var monthList = new List() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + await using var jazzDbContext = GetDbContext(cancellationToken).Result; + try + { + return (from chargeCodeMember in chargeCodeMembers + from opportunityLevelMember in opportunityLevelMembers + from month in monthList + join chargeCode in jazzDbContext.ChargeCodes on IdParse(chargeCodeMember.Id) equals chargeCode.ChargeCodeId + select new CCOpportunityDetailQueryResult + { + ChargeCodeId = IdParse(chargeCodeMember.Id), + ChargeCodeName = chargeCodeMember.Name, + OpportunityLevelId = IdParse(opportunityLevelMember.Id), + ChargeCodeRollupName = chargeCode.Rollup, + IsBaseline = isBaseline, + Month = month + }).ToList(); + } + catch (Exception ex) + { + _logging.LogContextError(ex, jazzDbContext, nameof(CreateDetailsForAllChargeCodes), + new Dictionary + { + { nameof(chargeCodeMembers), chargeCodeMembers.ParamDictionary() }, + { nameof(opportunityLevelMembers), opportunityLevelMembers.ParamDictionary() }, + { nameof(isBaseline), isBaseline } + }); + } + return Enumerable.Empty(); + } + + private async Task IsUsingSnowflake(CancellationToken cancellationToken) + { + var databaseGuid = _user.GetDatabaseGuid(); + return await _featureFlagServiceClient.IsEnabledAsync("is-decision-support-queries-enabled", databaseGuid, cancellationToken: cancellationToken); + } + + private async Task GetScoreDimensionFriendlyName(Guid dimensionGuid) + { + var dimension = await _schemaServiceClient.GetDimensionByIdAsync(dimensionGuid); + return dimension.FriendlyName; + } + + private async Task GetScoreAttributeFriendlyName(Guid dimensionGuid, CancellationToken cancellationToken) + { + await using var jazzDbContext = GetDbContext(cancellationToken).Result; + var dimension = await jazzDbContext.ViewScoreAttributes.Where(dim => dim.DimensionGuid == dimensionGuid).FirstOrDefaultAsync(); + return dimension.FriendlyName; + } + + private static List GetSQLParameters(CCOpportunityDataModel opportunity, Configuration.Configuration configuration, bool isBaseline) + { + var sqlParams = new List(); + + if (opportunity.OpportunityLevelEnum != OpportunityLevel.Organization) + { + sqlParams.Add(new SqlParameter("@oppLevelIds", opportunity.OpportunityLevelSelections.GetIdsForMemberDtos())); + } + + if (!opportunity.ClinicalIndicatorSelections.IsEmpty()) + { + sqlParams.Add(new SqlParameter("@indicatorIds", opportunity.ClinicalIndicatorSelections.GetIdsForMemberDtos())); + } + else if (!opportunity.CaseTypeSelections.IsEmpty()) + { + sqlParams.Add(new SqlParameter("@caseTypeIds", opportunity.CaseTypeSelections.GetIdsForMemberDtos())); + } + + if (!opportunity.ChargeCodeSelections.IsEmpty()) + { + sqlParams.Add(new SqlParameter("@chargeCodeIds", opportunity.ChargeCodeSelections.GetIdsForMemberDtos())); + } + + var startDate = isBaseline + ? configuration.GetBaselineStartDate(OpportunityTypes.ChargeCode) + : configuration.GetTrackingStartDate(OpportunityTypes.ChargeCode); + + var endDate = isBaseline + ? configuration.GetBaselineEndDate(OpportunityTypes.ChargeCode) + : configuration.GetTrackingEndDate(); + + sqlParams.Add(new SqlParameter("@startDate", startDate.ToString("yyyy-MM-dd"))); + sqlParams.Add(new SqlParameter("@endDate", endDate.ToString("yyyy-MM-dd"))); + sqlParams.Add(new SqlParameter("@isBaseline", isBaseline)); + + return sqlParams; + } + + } + +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailTreeGridModel.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailTreeGridModel.cs new file mode 100644 index 0000000..73fdc8c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityDetailTreeGridModel.cs @@ -0,0 +1,100 @@ +using Microsoft.VisualBasic; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.Shared; +using AppConstants = Strata.ContinuousImprovement.Biz.Utilities.Constants; +using System; +using System.Collections.Generic; +using System.Linq; +using MoreLinq.Extensions; +using Strata.ContinuousImprovement.Biz.Utilities; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class CCOpportunityDetailTreeGridModel + { + public string Key { get; set; } + public CCOpportunityDetailRowData Data { get; set; } + public IEnumerable Children { get; set; } + + public CCOpportunityDetailTreeGridModel( + IGrouping opportunityLevelGrouping, + string parentKey, + Func, string> getChildName, + IEnumerable levelCases, + TypeOfChildren typeOfChildren = TypeOfChildren.Rollup) + { + + var key = opportunityLevelGrouping.Key.Replace(AppConstants.TREE_DATA_LEVEL_SEPARATOR, String.Empty); + Key = string.IsNullOrEmpty(parentKey) ? key : parentKey + AppConstants.TREE_DATA_LEVEL_SEPARATOR + key; + var name = (Key == OpportunityLevel.Organization.ToString()) ? "System" : getChildName(opportunityLevelGrouping); + + int totalCases = GetTotalCases(opportunityLevelGrouping, levelCases, typeOfChildren); + double goalCostPerUnit = GetGoalCostPerUnit(opportunityLevelGrouping, typeOfChildren); + + Data = new CCOpportunityDetailRowData( + name, + totalCases, + opportunityLevelGrouping.Sum(detail => detail.TotalCost), + opportunityLevelGrouping.Sum(detail => detail.TotalUnitsOfService), + opportunityLevelGrouping.Sum(detail => detail.GoalTotalUnitsOfService), + goalCostPerUnit + ); + + switch (typeOfChildren) + { + case TypeOfChildren.Rollup: + Children = opportunityLevelGrouping + .GroupBy(detail => detail.ChargeCodeRollupName) + .Select(rollupGrouping => new CCOpportunityDetailTreeGridModel(rollupGrouping, Key, + rollupGrouping => rollupGrouping.First().ChargeCodeRollupName, levelCases, TypeOfChildren.ChargeCode)); + break; + case TypeOfChildren.ChargeCode: + Children = opportunityLevelGrouping + .GroupBy(detail => detail.ChargeCodeName) + .Select(rollupGrouping => new CCOpportunityDetailTreeGridModel(rollupGrouping, Key, + rollupGrouping => rollupGrouping.First().ChargeCodeName, levelCases, TypeOfChildren.None)); + break; + default: + Children = new List(); + break; + } + } + + private static int GetTotalCases(IGrouping opportunityLevelGrouping, IEnumerable levelCases, TypeOfChildren typeOfChildren) + { + int totalCases = 0; + if (typeOfChildren == TypeOfChildren.Rollup) + { + var levelId = opportunityLevelGrouping.Select(x => x.OpportunityLevelId).FirstOrDefault(); + totalCases = levelCases.Where(levelCases => levelCases.OpportunityLevelId == levelId).Sum(levelCases => levelCases.Cases); + + } + else + { + totalCases = opportunityLevelGrouping.Sum(detail => detail.TotalCases); + } + + return totalCases; + } + + private static double GetGoalCostPerUnit(IGrouping grouping, TypeOfChildren typeOfChildren) + { + if (typeOfChildren == TypeOfChildren.None) + { + return grouping.First().GoalCostPerUnit; + } + + var goalTotalCost = grouping.Sum(detail => detail.GoalCostPerUnit * detail.GoalTotalUnitsOfService); + var goalTotalUnitsOfService = grouping.Sum(detail => detail.GoalTotalUnitsOfService); + + return goalTotalCost.DivideBy(goalTotalUnitsOfService); + } + } + + public enum TypeOfChildren + { + None, + Rollup, + ChargeCode + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityLevelCaseQueryResult.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityLevelCaseQueryResult.cs new file mode 100644 index 0000000..ce380ad --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityLevelCaseQueryResult.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class CCOpportunityLevelCaseQueryResult + { + public int Month { get; set; } + public int OpportunityLevelId { get; set; } + public int TotalCases { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityLevelCaseService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityLevelCaseService.cs new file mode 100644 index 0000000..65c37b8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityLevelCaseService.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class CCOpportunityLevelCaseService : ICCOpportunityLevelCaseService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + + public CCOpportunityLevelCaseService(IAsyncDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task AddOrUpdateLevelCases(List levelCases, CancellationToken cancellationToken) + { + if (levelCases?.Count > 0) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + + foreach (var levelCase in levelCases) + { + var exists = await dbContext.ChargeCodeOpportunityLevelCases + .AnyAsync(x => x.OpportunityGuid == levelCase.OpportunityGuid + && x.OpportunityLevelId == levelCase.OpportunityLevelId + && x.Month == levelCase.Month + && x.IsBaseline == levelCase.IsBaseline); + if (exists) + { + dbContext.ChargeCodeOpportunityLevelCases.Update(levelCase); + } + else + { + await dbContext.ChargeCodeOpportunityLevelCases.AddAsync(levelCase); + } + } + await dbContext.SaveChangesAsync(); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityNotFoundException.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityNotFoundException.cs new file mode 100644 index 0000000..8f16f8c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityNotFoundException.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.Serialization; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + [Serializable] + public class CCOpportunityNotFoundException : Exception + { + public string Name { get; set; } + public Guid OpportunityGuid { get; set; } + protected CCOpportunityNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base(serializationInfo, streamingContext) + { + + } + + public CCOpportunityNotFoundException(string name, Guid opportunityGuid) + { + Name = name; + OpportunityGuid = opportunityGuid; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunitySaveModel.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunitySaveModel.cs new file mode 100644 index 0000000..f590179 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunitySaveModel.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class CCOpportunitySaveModel + { + public Guid OpportunityGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public bool IsSeasonal { get; set; } + public string ChargeCodeJson { get; set; } + public int OpportunityLevel { get; set; } + public string OpportunityLevelJson { get; set; } + + [Required] + public int EncounterGroup { get; set; } + + [NotMapped] + public bool IsFromWizard { get; set; } + [NotMapped] + public IEnumerable EncounterGroups { get; set; } + [NotMapped] + public IEnumerable ChargeCodes { get; set; } + [NotMapped] + public IEnumerable OpportunityLevels { get; set; } + } +} + diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityService.cs new file mode 100644 index 0000000..2fda39b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityService.cs @@ -0,0 +1,590 @@ +using ClosedXML.Excel; +using Hangfire; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MoreLinq; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.ChargeCode.Jobs; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.Id.Client; +using Strata.Schema.Client; +using Strata.Schema.Client.Dtos; +using Strata.Schema.Client.Dtos.Info; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class CCOpportunityService : OpportunityService, ICCOpportunityService + { + private readonly IConfigurationService _configurationService; + private readonly IAuthorizationServiceClient _authorizationServiceClient; + private readonly IIdServiceClient _idServiceClient; + private readonly IFiscalMonthResolver _fiscalMonthResolver; + private readonly ISchemaServiceClient _schemaServiceClient; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly IHubContext _notificationHubContext; + private ChargeCodeOpportunityValidationSection _validationSection; + private readonly IInitiativeService _initiativeService; + private readonly ISnowflakeDatabaseContext _snowflakeDbContext; + private readonly ILogger _logging; + + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + +#pragma warning disable S107 // Constructor has NN new parameters, which is greater than the 7 authorized + public CCOpportunityService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IConfigurationService configurationService, + IAuthorizationServiceClient authorizationServiceClient, + IIdServiceClient idServiceClient, + IFiscalMonthResolver fiscalMonthResolver, + ISchemaServiceClient schemaServiceClient, + IBackgroundJobClient backgroundJobClient, + IHubContext notificationHubContext, + IOptions sectionAccessor, + IInitiativeService initiativeService, + ISnowflakeDatabaseContext snowflakeDatabaseContext, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger logging) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) +#pragma warning restore S107 + { + _configurationService = configurationService; + _authorizationServiceClient = authorizationServiceClient; + _idServiceClient = idServiceClient; + _fiscalMonthResolver = fiscalMonthResolver; + _schemaServiceClient = schemaServiceClient; + _backgroundJobClient = backgroundJobClient; + _notificationHubContext = notificationHubContext; + _validationSection = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(ChargeCodeOpportunityValidationSection)); + _initiativeService = initiativeService; + _snowflakeDbContext = snowflakeDatabaseContext; + _logging = logging; + } + + public async Task ValidateChargeCodesAsync(List chargeCodes, CancellationToken cancellationToken) + { + try + { + var memberNodes = await GetDimensionMembers("Charge Code", chargeCodes, cancellationToken); + var maxLimit = _validationSection.MaxChargeCodeSelections; + + return memberNodes.Nodes.Sum(x => x.Count) <= maxLimit; + } + catch (Exception ex) + { + _logging.LogServiceError(ex, "Charge code validation failed"); + return false; + } + } + + public async Task> GetChargeCodePickerModelMembers(List hPaths, CancellationToken cancellationToken) + { + var dimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Charge Code"); + var chargeCodeMembers = new List(); + foreach (var codeHpaths in hPaths.Batch(250)) + { + var members = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, codeHpaths, cancellationToken); + foreach (var member in members.Batch(250)) // prevent from exception + { + chargeCodeMembers.AddRange(await _schemaServiceClient.GetFilteredDimensionMembersByGlobalIdAsync("Charge Code", + member.Select(m => Int32.Parse(m.Id)).ToList())); + } + } + return chargeCodeMembers.DistinctBy(cc => cc.ChargeCodeId).ToList(); + } + + public async Task ValidateOpportunityLevelsAsync(OpportunityLevel opportunityLevel, List opportunityLevels, Guid configurationGuid, CancellationToken cancellationToken) + { + try + { + var configuration = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + var memberNodes = await GetOpportunityLevelMemberInfos(opportunityLevel, opportunityLevels, configuration, cancellationToken); + return memberNodes.Nodes.Sum(x => x.Count) <= _validationSection.MaxOpportunityLevelSelections; + } + catch (Exception ex) + { + _logging.LogServiceError(ex, "Opportunity level validation failed"); + return false; + } + } + + public async Task CreateAsync(Guid databaseGuid, CCOpportunityDataModel opportunity, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(opportunity.Name); + try + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + await using var dbContext = GetDbContext(cancellationToken).Result; + var configuration = await _configurationService.GetConfigurationAsync(opportunity.ConfigurationGuid, cancellationToken); + opportunity.Configuration = configuration; + opportunity.Configuration.FiscalMonthResolver = _fiscalMonthResolver; + + await SetChargeCodeSelections(opportunity, cancellationToken); + await SetEncounterGroupSelectionJSON(opportunity, cancellationToken); + await SetOpportunityLevelSelectionJSON(opportunity, cancellationToken); + + dbContext.ChargeCodeOpportunities.Add(opportunity); + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(opportunity.OpportunityGuid, SecurableEntityType.Opportunity, cancellationToken); + + await dbContext.SaveChangesAsync(cancellationToken); + + await hub.SendOpportunityCreateDetails(opportunity.Name, 0); + var jobId = _backgroundJobClient.Enqueue(job + => job.PopulateOpportunityDetailsAsync(strataId, opportunity.Name, opportunity.OpportunityGuid, true, true, false, cancellationToken)); + return jobId; + } + catch (Exception ex) + { + _logging.LogOpportunityError(ex, opportunity.Name, opportunity.OpportunityGuid); + await hub.SendOpportunityCreateError(opportunity.Name, ex.Message); + return null; + } + } + + public async Task UpdateAsync(Guid databaseGuid, CCOpportunityDataModel opportunity, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(opportunity.Name); + var opportunityGuid = opportunity.OpportunityGuid; + try + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var originalOpportunity = await dbContext.ChargeCodeOpportunities + .FirstOrDefaultAsync(opp => opp.OpportunityGuid.Equals(opportunityGuid), cancellationToken) + ?? throw new CCOpportunityNotFoundException(opportunity.Name, opportunity.OpportunityGuid); + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + var configuration = await _configurationService.GetConfigurationAsync(opportunity.ConfigurationGuid, cancellationToken); + opportunity.Configuration = configuration; + opportunity.Configuration.FiscalMonthResolver = _fiscalMonthResolver; + + await SetChargeCodeSelections(opportunity, cancellationToken); + await SetEncounterGroupSelectionJSON(opportunity, cancellationToken); + await SetOpportunityLevelSelectionJSON(opportunity, cancellationToken); + + var isDetailsRefreshNeeded = opportunity.IsDetailsShouldBeUpdated(originalOpportunity); + if (isDetailsRefreshNeeded) + { + var opportunityDetails = await dbContext.ChargeCodeDetails.Where(details => details.OpportunityGUID.Equals(opportunityGuid)).ToListAsync(); + dbContext.RemoveRange(opportunityDetails); + + var opportunityLevelCases = await dbContext.ChargeCodeOpportunityLevelCases.Where(levelCases => levelCases.OpportunityGuid.Equals(opportunityGuid)).ToListAsync(); + dbContext.RemoveRange(opportunityLevelCases); + } + + originalOpportunity.Name = opportunity.Name; + originalOpportunity.ChargeCodeJson = opportunity.ChargeCodeJson; + originalOpportunity.CaseTypeJson = opportunity.CaseTypeJson; + originalOpportunity.ClinicalIndicatorJson = opportunity.ClinicalIndicatorJson; + originalOpportunity.OpportunityLevelJson = opportunity.OpportunityLevelJson; + originalOpportunity.OpportunityLevel = opportunity.OpportunityLevel; + originalOpportunity.IsSeasonal = opportunity.IsSeasonal; + + await dbContext.SaveChangesAsync(cancellationToken); + + if (isDetailsRefreshNeeded) + { + var jobId = _backgroundJobClient.Enqueue(job + => job.PopulateOpportunityDetailsAsync(strataId, opportunity.Name, opportunityGuid, true, true, true, cancellationToken)); + return jobId; + } + + await hub.SendOpportunityCreateUpdateSuccess(opportunity.OpportunityGuid, true, opportunity.Name, true, OpportunityTypes.ChargeCode); + return Guid.Empty.ToString(); + } + catch (CCOpportunityNotFoundException nf) + { + await hub.SendOpportunityUpdateError(nf.Name); + return null; + } + catch (Exception ex) + { + _logging.LogOpportunityError(ex, opportunity.Name, opportunityGuid); + await hub.SendOpportunityUpdateError(opportunity.Name); + return null; + } + } + + public override async Task GetById(Guid opportunityGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var opportunity = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .FirstOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunity == null) { return opportunity; } + opportunity.Configuration = await dbContext.Configurations.SingleOrDefaultAsync(c => c.ConfigurationGuid == opportunity.ConfigurationGuid, cancellationToken); + opportunity.Configuration.FiscalMonthResolver = _fiscalMonthResolver; + opportunity.Details = await dbContext.ChargeCodeDetails.Where(detail => detail.OpportunityGUID == opportunity.OpportunityGuid && detail.IsBaseline == true).ToListAsync(cancellationToken); + opportunity.LevelCases = await dbContext.ChargeCodeOpportunityLevelCases.Where(cases => cases.OpportunityGuid == opportunity.OpportunityGuid && cases.IsBaseline).ToListAsync(cancellationToken); + opportunity.EncounterGroup = (int)(opportunity.CaseTypeSelections?.Any() ?? false ? ChargeCodeGroup.CaseType : ChargeCodeGroup.PatientPopulation); + if (opportunity is IWithSourceOpportunity && _centralDbContext != null) + { + try + { + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities) + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(cno => cno.Opportunities.Any(link => link.OpportunityGuid == opportunity.OpportunityGuid)); + ((IWithSourceOpportunity)opportunity).SetSourceOpportunity(clientNetworkOpportunity); + } + catch (Exception ex) + { + _logging.LogWarning(ex, "Failed to load source opportunity for {OpportunityGuid}", opportunityGuid.ToString()); + throw; + } + } + return opportunity; + } + + public async Task GetInitiativeInfoAsync(Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) { return default; } + var opportunityItem = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunityItem == null) { return default; } + var configuration = await _configurationService.GetConfigurationAsync(opportunityItem.ConfigurationGuid, cancellationToken); + + if (opportunityItem.InitiativeGuid != Guid.Empty) + { + var initiative = dbContext?.Initiatives.SingleOrDefault(i => i.InitiativeGUID == opportunityItem.InitiativeGuid); + var fiscalMonths = configuration.GetTrackingFiscalMonths(OpportunityTypes.OrgDefined, initiative.EffectiveDate); + return initiative?.InitiativeInfo(opportunityItem.OpportunityGuid, fiscalMonths, configuration.OrgDefinedTrackingStartDate, configuration.OrgDefinedTrackingEndDate); + } + var info = new InitiativeInfo + { + OpportunityGuid = opportunityGuid, + Name = opportunityItem.Name, + CostDriver = "Charge Code", + StartMonth = configuration.OrgDefinedTrackingStartDate.ToString("yyyy-MM-dd"), + EndMonth = configuration.OrgDefinedTrackingEndDate.ToString("yyyy-MM-dd") + }; + return info; + } + + public async Task RefreshOpportunityTrackingData(Guid databaseGuid, Guid opportunityGuid, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + return _backgroundJobClient.Enqueue(job + => job.RefreshClientOpportunityTrackingDetails(strataId, opportunityGuid, cancellationToken)); + } + + internal static async Task CheckForDuplicateName(CCOpportunityDataModel opportunity, JazzDbContext dbContext, CancellationToken cancellationToken) + { + var nameExists = await dbContext.ChargeCodeOpportunities.AnyAsync(x => x.Name == opportunity.Name, cancellationToken); + if (nameExists) + { + throw new DuplicateNameException(); + } + } + + public async Task ExportAsync(Guid opportunityGuid, IEnumerable chargeCodes, string user, CancellationToken cancellationToken) + { + var memberInfo = await GetDimensionMembers("Charge Code", chargeCodes, cancellationToken); + var ids = memberInfo.Members.Select(m => int.TryParse(m.Id, out var res) ? res : 0).Where(id => id > 0).ToList(); + var results = ids.Any() + ? await _schemaServiceClient.GetFilteredDimensionMembersByGlobalIdAsync("Charge Code", ids) + : new List(); + + var exportChargeCodes = results.Select(r => r.ChargeCode).ToList(); + if (opportunityGuid != Guid.Empty) + { + var dbContext = await GetDbContext(cancellationToken); + var opportunity = dbContext.ChargeCodeOpportunities.FirstOrDefault(opp => opp.OpportunityGuid == opportunityGuid); + var existingChargeCodes = opportunity?.ChargeCodes.ToList() ?? new List(); + exportChargeCodes.AddRange(existingChargeCodes); + } + var workbook = new XLWorkbook(); + var ws = workbook.AddWorksheet("Charge Codes"); + ws.Cell(1, 1).InsertData(exportChargeCodes.Distinct().OrderBy(x => x)); + return workbook; + } + + internal class FilteredResult + { + public string ChargeCode { get; set; } + } + + public async Task UpdateGoalTotalUnitsOfServiceAsync(Guid opportunityGuid, string key, double goalValue, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return false; + var opportunity = await GetChargeCodeOpportunityWithDetails(opportunityGuid, key, cancellationToken); + var opportunityDetails = opportunity?.Details; + if (opportunityDetails == null || !opportunityDetails.Any()) return false; + + var totalCases = opportunityDetails.Sum(d => d.TotalCases); + opportunityDetails.ForEach(detail => + { + detail.GoalTotalUnitsOfService = (opportunity.IsSeasonal && totalCases != 0) + ? goalValue * (detail.TotalCases.DivideBy(totalCases)) + : goalValue.DivideBy(12); + }); + dbContext.ChargeCodeDetails.UpdateRange(opportunityDetails); + return (await dbContext.SaveChangesAsync(cancellationToken)) > 0; + } + + public async Task UpdateGoalCostPerUnitAsync(Guid opportunityGuid, string key, double goalValue, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return false; + var opportunity = await GetChargeCodeOpportunityWithDetails(opportunityGuid, key, cancellationToken); + var opportunityDetails = opportunity.Details; + if (opportunityDetails == null) return false; + + opportunityDetails.ForEach(detail => detail.GoalCostPerUnit = goalValue); + dbContext.ChargeCodeDetails.UpdateRange(opportunityDetails); + return (await dbContext.SaveChangesAsync(cancellationToken)) > 0; + } + + public async Task UpdateGoalsAsync(Guid opportunityGuid, IEnumerable changes, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return false; + + var opportunity = await dbContext.ChargeCodeOpportunities.SingleOrDefaultAsync(cc => cc.OpportunityGuid.Equals(opportunityGuid)); + if (opportunity == null) return false; + + foreach (var change in changes) + { + var opportunityDetails = await GetChargeCodeOpportunityDetails(opportunityGuid, change.Key, cancellationToken); + if (opportunityDetails == null) continue; + + opportunityDetails = opportunityDetails.Where(x => x.IsBaseline).ToList(); + + if (change.GoalCostPerUnit.HasValue) opportunityDetails.ForEach(detail => detail.GoalCostPerUnit = change.GoalCostPerUnit.Value); + + if (change.GoalTotalUnitsOfService.HasValue) + { + var goalValue = change.GoalTotalUnitsOfService.Value; + var totalCases = opportunityDetails.Sum(d => d.TotalCases); + opportunityDetails.ForEach(detail => + { + detail.GoalTotalUnitsOfService = (opportunity.IsSeasonal && totalCases != 0) + ? goalValue * (detail.TotalCases.DivideBy(totalCases)) + : goalValue.DivideBy(12); + }); + } + + dbContext.ChargeCodeDetails.UpdateRange(opportunityDetails); + } + + var changesNumber = await dbContext.SaveChangesAsync(cancellationToken); + return changesNumber > 0; + } + + private async Task GetChargeCodeOpportunityWithDetails(Guid opportunityGuid, string key, CancellationToken cancellationToken) + { + if (!key.Contains('|')) { return default; } + try + { + var keys = key.Split('|').Select(k => k.Trim()).ToArray(); + var keySplit = new + { + OpportunityLevelId = Int32.TryParse(keys[0].Trim(), out var id) ? id : 0, + ChargeCodeRollupName = keys[1].Trim(), + ChargeCodeName = keys[2].Trim(), + + }; + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return default; + + var opportunity = await dbContext.ChargeCodeOpportunities + .SingleOrDefaultAsync(cc => cc.OpportunityGuid.Equals(opportunityGuid)); + if (opportunity != null) + { + opportunity.Details = await dbContext.ChargeCodeDetails + .Where(detail => detail.OpportunityGUID == opportunityGuid + && detail.OpportunityLevelId == keySplit.OpportunityLevelId + && detail.ChargeCodeRollupName == keySplit.ChargeCodeRollupName + && detail.ChargeCodeName == keySplit.ChargeCodeName) + .ToListAsync(cancellationToken); + } + return opportunity; + } + catch (Exception ex) + { + _logging.LogServiceError(ex); + return default; + } + } + + private async Task> GetChargeCodeOpportunityDetails(Guid opportunityGuid, string key, CancellationToken cancellationToken) + { + if (!key.Contains('|')) { return default; } + try + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return default; + + var keys = key.Split('|').Select(k => k.Trim()).ToArray(); + var keySplit = new + { + OpportunityLevelId = Int32.TryParse(keys[0].Trim(), out var id) ? id : 0, + ChargeCodeRollupName = keys[1].Trim(), + ChargeCodeName = keys[2].Trim(), + + }; + + var details = await dbContext.ChargeCodeDetails + .Where(detail => detail.OpportunityGUID == opportunityGuid + && detail.OpportunityLevelId == keySplit.OpportunityLevelId + && detail.ChargeCodeRollupName == keySplit.ChargeCodeRollupName + && detail.ChargeCodeName == keySplit.ChargeCodeName) + .ToListAsync(cancellationToken); + + return details; + } + catch (Exception ex) + { + _logging.LogServiceError(ex); + return default; + } + } + + private async Task SetEncounterGroupSelectionJSON(CCOpportunityDataModel opportunity, CancellationToken cancellationToken) + { + NodesAndMembers memberInfo = default; + + if (opportunity.EncounterGroup == (int)EncounterGroup.CaseType) + { + memberInfo = await GetDimensionMembers("Case Type", opportunity.CaseTypes, cancellationToken); + opportunity.CaseTypeJson = JsonSerializer.Serialize(new { memberInfo.HierarchyPaths, memberInfo.Members }); + } + else + { + memberInfo = await GetDimensionMembers("Clinical Indicator", opportunity.PatientPopulations, cancellationToken); + opportunity.ClinicalIndicatorJson = JsonSerializer.Serialize(new { memberInfo.HierarchyPaths, memberInfo.Members }); + } + } + + private async Task SetChargeCodeSelections(CCOpportunityDataModel opportunity, CancellationToken cancellationToken) + { + var memberInfo = await GetDimensionMembers("Charge Code", opportunity.ChargeCodes, cancellationToken); + opportunity.ChargeCodeJson = JsonSerializer.Serialize(new { memberInfo.HierarchyPaths, memberInfo.Members }); + } + + private async Task SetOpportunityLevelSelectionJSON(CCOpportunityDataModel opportunity, CancellationToken cancellationToken) + { + NodesAndMembers memberInfo = null; + var opportunityLevel = Enum.TryParse(opportunity.OpportunityLevel, out var level) + ? level : OpportunityLevel.Organization; + var configuration = opportunity.Configuration; + + memberInfo = await GetOpportunityLevelMemberInfos(opportunityLevel, opportunity.OpportunityLevels, configuration, cancellationToken); + opportunity.OpportunityLevelJson = JsonSerializer.Serialize(new { memberInfo.HierarchyPaths, memberInfo.Members }); + } + + private async Task GetOpportunityLevelMemberInfos(OpportunityLevel opportunityLevel, List opportunityLevels, Configuration.Configuration configuration, CancellationToken cancellationToken) + { + var memberInfo = new NodesAndMembers(); + switch (opportunityLevel) + { + case OpportunityLevel.Entity: + var globalId = configuration.IsUsingCustomEntityDimension ? configuration.DepartmentRollupGlobalId : "Entity"; + memberInfo = await GetDimensionMembers(globalId, opportunityLevels, cancellationToken); + break; + case OpportunityLevel.ServiceLine: + var dimension = await _schemaServiceClient.GetDimensionByIdAsync(configuration.ServiceLineDimensionGuid) + ?? await _schemaServiceClient.GetDimensionByGlobalIdAsync("Service Line"); + memberInfo = await GetDimensionMembers(dimension, opportunityLevels, cancellationToken); + break; + case OpportunityLevel.Specialty: + memberInfo = await GetDimensionMembers("DSS Physician Specialty", opportunityLevels, cancellationToken); + break; + case OpportunityLevel.Provider: + memberInfo = await GetDimensionMembers("Physician Primary", opportunityLevels, cancellationToken); + break; + default: + memberInfo = NodesAndMembers.SystemNodeAndMember(); + break; + } + + return memberInfo; + } + + private async Task GetDimensionMembers(string globalId, IEnumerable codes, CancellationToken cancellationToken) + { + var dimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync(globalId); + return await GetDimensionMembers(dimension, codes, cancellationToken); + } + + private async Task GetDimensionMembers(ScoreDimensionInfoDto dimension, IEnumerable codes, CancellationToken cancellationToken) + { + var dimensionGuid = dimension.DimensionGuid; + var nodesAndMembers = new NodesAndMembers(); + nodesAndMembers.HierarchyPaths = codes.ToList(); + + // Batching is necessary when users are selecting leaf nodes. + foreach (var codeHpaths in codes.Batch(250)) + { + var nodes = await _schemaServiceClient.GetHierarchyNodesFromHierarchyPathsAsync(dimensionGuid, codeHpaths, cancellationToken); + var members = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimensionGuid, codeHpaths, cancellationToken); + nodesAndMembers.Nodes.AddRange(nodes); + nodesAndMembers.Members.AddRange(members); + } + return nodesAndMembers; + } + + internal class NodesAndMembers + { + public List HierarchyPaths { get; set; } + public List Nodes { get; set; } + public List Members { get; set; } + + public NodesAndMembers() + { + Nodes = new List(); + Members = new List(); + } + + public static NodesAndMembers SystemNodeAndMember() + => new NodesAndMembers + { + Nodes = new List{ + new HierarchyNodeDto + { + Name = "System", + Count = 1, + Path ="" + } + }, + Members = new List + { + new MemberDto + { + Id = "0", + Name = "System", + MemberGuid = Guid.Empty + } + } + }; + } + } + public class ChargeCodePickerModel + { + public int ChargeCodeId { get; set; } + public string Name { get; set; } + public string CostDriver { get; set; } + public string Rollup { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityViewService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityViewService.cs new file mode 100644 index 0000000..018c126 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/CCOpportunityViewService.cs @@ -0,0 +1,91 @@ +using ClosedXML.Excel; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class CCOpportunityViewService : OpportunityService, ICCOpportunityViewService + { + private readonly ILogger _logger; + + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public CCOpportunityViewService( + IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IInitiativeService initiativeService, IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger logger) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) + { + _logger = logger; + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return null; + + var opportunityContext = dbContext.Set(); + var configuredOpportunities = opportunityContext.Where(en => en.ConfigurationGuid == configurationGuid); + + var opportunityLevels = configuredOpportunities +#pragma warning disable S2971 // "IEnumerable" LINQs should be simplified + .Select(en => en.OpportunityLevel).Distinct().ToList() +#pragma warning restore S2971 // "IEnumerable" LINQs should be simplified + .Select(ol => new FilterMember { Id = ol, Text = (ol == "ServiceLine" ? "Service Line" : ol) }) + .OrderBy(ol => ol.Text); + + return new CCFilterOptions + { + OpportunityLevels = opportunityLevels + }; + } + + + public async Task ExportAsync(SortOptions sortOptions, CCFilters filters, CancellationToken cancellationToken) + { + var exportOptions = this.GetExportOptions(); + + // get data + var opportunities = await this.GetAllAsync(sortOptions, filters, cancellationToken); + var data = opportunities.Select(opp => new + { + Opportunity = opp.OpportunityKey, + opp.Name, + opp.IdentifiedSavings, + opp.Cases, + ChargeCodeGroup = opp.ChargeCodeGroupValue, + Level = opp.OpportunityLevel, + Seasonality = opp.IsSeasonal ? "With" : "Without", + opp.Initiative, + IsHidden = opp.IsHidden ? 1 : 0, + opp.Note + }); + + // configure column formats + exportOptions.AddColumnOptions("Identified Savings", "Currency2Dec"); + exportOptions.AddColumnOptions("Cases", "Accounting"); + exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\""); + + return ExportUtils.CreateExcelWorkbook(data, exportOptions); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ChargeCodeOpportunityDetailChanges.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ChargeCodeOpportunityDetailChanges.cs new file mode 100644 index 0000000..e93aed9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ChargeCodeOpportunityDetailChanges.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class ChargeCodeOpportunityDetailChanges + { + public string Key { get; set; } + public double? GoalTotalUnitsOfService { get; set; } + public double? GoalCostPerUnit { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ChargeCodeOpportunityLevelCase.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ChargeCodeOpportunityLevelCase.cs new file mode 100644 index 0000000..462dc38 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ChargeCodeOpportunityLevelCase.cs @@ -0,0 +1,15 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + [Table("ChargeCodeOpportunityLevelCase", Schema = "cci")] + public class ChargeCodeOpportunityLevelCase + { + public Guid OpportunityGuid { get; set; } + public bool IsBaseline { get; set; } + public int OpportunityLevelId { get; set; } + public int Month { get; set; } + public int Cases { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/EncounterGroupQueryHelper.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/EncounterGroupQueryHelper.cs new file mode 100644 index 0000000..af022c7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/EncounterGroupQueryHelper.cs @@ -0,0 +1,41 @@ +using Microsoft.Data.SqlClient; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class EncounterGroupQueryHelper : QueryHelper + { + public EncounterGroupQueryHelper(IEnumerable sqlParams) + { + var hasCaseTypeIdParams = sqlParams.Any(p => p.ParameterName.StartsWith("@caseTypeId", System.StringComparison.CurrentCultureIgnoreCase)); + var hasIndicatorIdParams = sqlParams.Any(p => p.ParameterName.StartsWith("@indicatorId", System.StringComparison.CurrentCultureIgnoreCase)); + if (hasCaseTypeIdParams) + { + Join = @"INNER JOIN dss.DimCaseType dct ON + CASE dct.casetypecategory + WHEN 0 THEN fpes.msdrgcasetypefamilyid + WHEN 1 THEN fpes.aprdrgcasetypefamilyid + WHEN 3 THEN fpes.cptcodecasetypefamilyid + ELSE -1 END = dct.casetypefamilyid"; + // Case Type Id is unique so the categories should be mutually exclusive as to which CaseTypeCategory the Id belongs. + WhereClause = $@"AND dct.casetypeid in (@caseTypeIds) + AND ((dct.CaseTypeCategory = 0 AND EXISTS(SELECT 1 + FROM dss.CaseTypeFamilyMappingICD10 fid + WHERE fid.CaseTypeFamilyID = dct.CaseTypeFamilyID)) + OR (dct.CaseTypeCategory = 1 AND EXISTS(SELECT 1 + FROM dss.CaseTypeFamilyMappingAPRDRG fid + WHERE fid.CaseTypeFamilyID = dct.CaseTypeFamilyID)) + OR (dct.CaseTypeCategory = 3 AND EXISTS(SELECT 1 + FROM dss.CaseTypeFamilyMappingCPT fid + WHERE fid.CaseTypeFamilyID = dct.CaseTypeFamilyID)) + )"; + } + else if (hasIndicatorIdParams) + { + Join = "INNER JOIN dss.FactPatientEncounterClinicalIndicator ci on ci.EncounterID = fpes.EncounterID and ci.IsDeleted = 0"; + WhereClause = $"AND ci.ClinicalIndicatorId in (@indicatorIds)"; + } + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityDetailService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityDetailService.cs new file mode 100644 index 0000000..a52432c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityDetailService.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public interface ICCOpportunityDetailService + { + double CalculateSavings(IEnumerable details); + Task> GetCCOpportunityDetailQueryResult(CCOpportunityDataModel opportunity, + Configuration.Configuration configuration, CancellationToken cancellationToken, bool isBaseline = true); + Task> CCOpportunityLevelCasesQuery(CCOpportunityDataModel opportunity, + Configuration.Configuration configuration, CancellationToken cancellationToken, bool isBaseline = true); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityLevelCaseService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityLevelCaseService.cs new file mode 100644 index 0000000..9c80fe4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityLevelCaseService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public interface ICCOpportunityLevelCaseService + { + Task AddOrUpdateLevelCases(List levelCases, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityService.cs new file mode 100644 index 0000000..e6db281 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityService.cs @@ -0,0 +1,27 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public interface ICCOpportunityService : IOpportunityService + { + Task CreateAsync(Guid databaseGuid, CCOpportunityDataModel opportunity, CancellationToken cancellationToken); + Task UpdateAsync(Guid databaseGuid, CCOpportunityDataModel opportunity, CancellationToken cancellationToken); + Task ValidateChargeCodesAsync(List chargeCodes, CancellationToken cancellationToken); + Task> GetChargeCodePickerModelMembers(List hPaths, CancellationToken cancellationToken); + Task ValidateOpportunityLevelsAsync(OpportunityLevel opportunityLevel, List opportunityLevels, Guid configurationGuid, CancellationToken cancellationToken); + Task GetById(Guid opportunityGuid, CancellationToken cancellationToken); + Task UpdateGoalTotalUnitsOfServiceAsync(Guid opportunityGuid, string key, double goalValue, CancellationToken cancellationToken); + Task UpdateGoalCostPerUnitAsync(Guid opportunityGuid, string key, double goalValue, CancellationToken cancellationToken); + Task UpdateGoalsAsync(Guid opportunityGuid, IEnumerable changes, CancellationToken cancellationToken); + Task GetInitiativeInfoAsync(Guid opportunityGuid, CancellationToken cancellationToken); + Task RefreshOpportunityTrackingData(Guid databaseGuid, Guid opportunityGuid, CancellationToken cancellationToken); + Task ExportAsync(Guid opportunityGuid, IEnumerable chargeCodes, string user, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityViewService.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityViewService.cs new file mode 100644 index 0000000..af3d0d5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/ICCOpportunityViewService.cs @@ -0,0 +1,18 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public interface ICCOpportunityViewService : IOpportunityService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + + Task ExportAsync(SortOptions sortOptions, CCFilters filters, CancellationToken cancellationToken); + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/OpportunityDetailLevelQuery.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/OpportunityDetailLevelQuery.cs new file mode 100644 index 0000000..49f8aa7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/OpportunityDetailLevelQuery.cs @@ -0,0 +1,39 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class OpportunityDetailLevelQuery + { + private readonly OpportunityLevel _opportunityLevel; + private readonly QueryHelper _encounterGroupQuery; + private readonly QueryHelper _opportunityLevelQuery; + + public OpportunityDetailLevelQuery(OpportunityLevel opportunityLevel, QueryHelper encounterGroupQuery, QueryHelper opportunityLevelQuery) + { + _opportunityLevel = opportunityLevel; + _encounterGroupQuery = encounterGroupQuery; + _opportunityLevelQuery = opportunityLevelQuery; + } + + public string GetQuery() + { + return $@" +SELECT + CAST(MONTH(fpes.DischargeDateTime) AS INT) Month + ,CAST({(_opportunityLevel is not OpportunityLevel.Organization ? _opportunityLevelQuery?.Id : "0")} AS INT) OpportunityLevelId + ,COUNT(DISTINCT fpes.EncounterId) AS TotalCases +FROM clientdss.FactPatientEncounterSummary fpes +{_encounterGroupQuery.Join} +{_opportunityLevelQuery?.Join} +WHERE + fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate + {_encounterGroupQuery.WhereClause} + {_opportunityLevelQuery?.WhereClause} +GROUP BY MONTH(fpes.DischargeDateTime) + {(_opportunityLevel is not OpportunityLevel.Organization ? $",{_opportunityLevelQuery?.Id}" : "")};".TightenUp(); + } + + } + +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/OpportunityDetailQuery.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/OpportunityDetailQuery.cs new file mode 100644 index 0000000..f6ac841 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/OpportunityDetailQuery.cs @@ -0,0 +1,100 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class OpportunityDetailQuery + { + private readonly OpportunityLevel _opportunityLevel; + private readonly QueryHelper _encounterGroupQuery; + private readonly QueryHelper _opportunityLevelQuery; + private readonly bool _isUsingSnowflake; + + public OpportunityDetailQuery(OpportunityLevel opportunityLevel, QueryHelper encounterGroupQuery, QueryHelper opportunityLevelQuery, bool isUsingSnowflake = false) + { + _opportunityLevel = opportunityLevel; + _encounterGroupQuery = encounterGroupQuery; + _opportunityLevelQuery = opportunityLevelQuery; + _isUsingSnowflake = isUsingSnowflake; + } + + public string GetQuery() + { + if (_isUsingSnowflake) + { + return $@" +SELECT + cc.ChargeCodeId + ,'' AS ChargeCodeName + ,cc.Rollup AS ChargeCodeRollupName + ,MONTH(fpes.DischargeDateTime) AS Month + {(_opportunityLevel is not OpportunityLevel.Organization ? $",CAST({_opportunityLevelQuery?.Id} AS INT) OpportunityLevelId" : ",0 OpportunityLevelId")} + ,'' AS OpportunityLevelName + ,COUNT(DISTINCT fpes.EncounterId) AS TotalCases + ,SUM(billing.UnitsOfService) AS TotalUnitsOfService + ,SUM(billing.VariableDirectCost) AS VariableDirectCost + ,SUM(billing.VariableIndirectCost) AS VariableIndirectCost + ,@isBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +INNER JOIN cci.EncounterChargeCodeCosts billing on billing.encounterid = fpes.encounterid +INNER JOIN fw.DimChargeCode cc ON billing.ChargeCodeId = cc.ChargeCodeId +{_encounterGroupQuery.Join} +{_opportunityLevelQuery?.Join} +WHERE + fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate + AND cc.ChargeCodeId in (@chargeCodeIds) + {_encounterGroupQuery.WhereClause} + {_opportunityLevelQuery?.WhereClause} +GROUP BY cc.ChargeCodeID, cc.Rollup, MONTH(fpes.DischargeDateTime) + {(_opportunityLevel is not OpportunityLevel.Organization ? $",{_opportunityLevelQuery?.Id}" : "")} + ; ".TightenUp(); + } + else + { + return $@" +WITH billing AS +( + SELECT + PBLID.* + ,COALESCE(PBLIC.FixedDirectUnitCost, 0) * PBLID.UnitsOfService FixedDirectCost + ,COALESCE(PBLIC.FixedIndirectUnitCost, 0) * PBLID.UnitsOfService FixedIndirectCost + ,COALESCE(PBLIC.VariableDirectUnitCost, 0) * PBLID.UnitsOfService VariableDirectCost + ,COALESCE(PBLIC.VariableIndirectUnitCost, 0) * PBLID.UnitsOfService VariableIndirectCost + ,(COALESCE(PBLIC.FixedDirectUnitCost, 0) + COALESCE(PBLIC.FixedIndirectUnitCost, 0)) * PBLID.UnitsOfService FixedCost + ,(COALESCE(PBLIC.VariableDirectUnitCost, 0) + COALESCE(PBLIC.VariableIndirectUnitCost, 0)) * PBLID.UnitsOfService VariableCost + ,(COALESCE(PBLIC.VariableDirectUnitCost, 0) + COALESCE(PBLIC.FixedDirectUnitCost, 0)) * PBLID.UnitsOfService DirectCost + ,(COALESCE(PBLIC.VariableIndirectUnitCost, 0) + COALESCE(PBLIC.FixedIndirectUnitCost, 0)) * PBLID.UnitsOfService IndirectCost + ,(COALESCE(PBLIC.VariableDirectUnitCost, 0) + COALESCE(PBLIC.VariableIndirectUnitCost, 0) + COALESCE(PBLIC.FixedDirectUnitCost, 0) + COALESCE(PBLIC.FixedIndirectUnitCost, 0)) * PBLID.UnitsOfService TotalCost + ,COALESCE(PBLIC.CostingResultID, 0) CostingResultID + FROM dss.FactPatientBillingLineItemDetail PBLID + LEFT JOIN dss.FactPatientBillingLineItemCosts PBLIC on PBLIC.PBLIDRowID = PBLID.RowID +) +SELECT + cc.ChargeCodeId + ,'' AS ChargeCodeName + ,cc.Rollup AS ChargeCodeRollupName + ,MONTH(fpes.DischargeDateTime) AS Month + {(_opportunityLevel is not OpportunityLevel.Organization ? $",CAST({_opportunityLevelQuery?.Id} AS INT) OpportunityLevelId" : ",0 OpportunityLevelId")} + ,'' AS OpportunityLevelName + ,COUNT(DISTINCT fpes.EncounterId) AS TotalCases + ,SUM(billing.UnitsOfService) AS TotalUnitsOfService + ,SUM(billing.VariableDirectCost) AS VariableDirectCost + ,SUM(billing.VariableIndirectCost) AS VariableIndirectCost + ,@isBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +INNER JOIN billing on billing.encounterid = fpes.encounterid +INNER JOIN fw.DimChargeCode cc ON billing.ChargeCodeId = cc.ChargeCodeId +{_encounterGroupQuery.Join} +{_opportunityLevelQuery?.Join} +WHERE + fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate + AND billing.UnitsOfService <> 0 + AND cc.ChargeCodeId in (@chargeCodeIds) + {_encounterGroupQuery.WhereClause} + {_opportunityLevelQuery?.WhereClause} +GROUP BY cc.ChargeCodeID, cc.[Rollup], MONTH(fpes.DischargeDateTime) + {(_opportunityLevel is not OpportunityLevel.Organization ? $",{_opportunityLevelQuery?.Id}" : "")};".TightenUp(); + } + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/OpportunityLevelQueryHelper.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/OpportunityLevelQueryHelper.cs new file mode 100644 index 0000000..22da1cc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/OpportunityLevelQueryHelper.cs @@ -0,0 +1,62 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using System; + +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class OpportunityLevelQueryHelper : QueryHelper + { + + public OpportunityLevelQueryHelper(OpportunityLevel opportunityLevel, Configuration.Configuration configuration, string serviceLineDimensionName, string serviceLineColumnName) + { + switch (opportunityLevel) + { + case OpportunityLevel.Entity: + if (configuration.DepartmentRollupGlobalId == "") + { + Id = "de.FwEntityID"; + Join = $"INNER JOIN clientdss.DimEntity de ON de.EntityID = fpes.EntityID"; + WhereClause = $"AND {Id} in (@oppLevelIds)"; + } + else if (configuration.DepartmentRollupGlobalId == "Location") + { + Id = "fpes.LocationID"; + WhereClause = $"AND {Id} in (@oppLevelIds)"; + } + else + { + var rollupLevel = configuration.DepartmentRollupGlobalId.Replace(" ", string.Empty); + Id = "department." + rollupLevel + "ID"; + Join = $"INNER JOIN fw.DimDepartment department ON fpes.HighestVariableCostDepartmentID = department.DepartmentId"; + WhereClause = $"AND {Id} in (@oppLevelIds)"; + } + break; + case OpportunityLevel.ServiceLine: + if (serviceLineDimensionName == "Sg2 Service Line Crosswalk") + { + Id = "sl.Sg2ServiceLineCrosswalkId"; + Join = @"INNER JOIN dss.DimSg2CareFamily cf ON cf.Sg2CAREFamilyID = fpes.Sg2CAREFamilyID +INNER JOIN dss.DimSg2ServiceLineCrosswalk sl ON sl.Sg2ServiceLine = cf.ServiceLine"; + WhereClause = $"AND {Id} in (@oppLevelIds)"; + } + else + { + Id = "fpes." + (String.IsNullOrEmpty(serviceLineColumnName) ? "ServiceLineID" : serviceLineColumnName); + WhereClause = $"AND {Id} in (@oppLevelIds)"; + } + break; + case OpportunityLevel.Specialty: + Id = "ps.PhysicianSpecialtyID"; + Join = @"INNER JOIN dss.DimPhysician pp ON pp.PhysicianID = fpes.PrimaryPhysicianID +INNER JOIN dss.DimPhysicianSpecialty ps ON ps.PhysicianSpecialtyID = pp.PhysicianSpecialtyID"; + WhereClause = $"AND {Id} in (@oppLevelIds)"; + break; + case OpportunityLevel.Provider: + Id = "fpes.PrimaryPhysicianID"; + WhereClause = $"AND {Id} in (@oppLevelIds)"; + break; + } + } + + } + +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/QueryHelper.cs b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/QueryHelper.cs new file mode 100644 index 0000000..c25d1f3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ChargeCode/Opportunities/QueryHelper.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities +{ + public class QueryHelper + { + public string Id { get; set; } = ""; + public string Join { get; set; } = ""; + public string WhereClause { get; set; } = ""; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ClientLicense/ClientLicenseService.cs b/src/Strata.ContinuousImprovement.Biz/ClientLicense/ClientLicenseService.cs new file mode 100644 index 0000000..10bf17c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ClientLicense/ClientLicenseService.cs @@ -0,0 +1,60 @@ +using Microsoft.Extensions.Options; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Id.Client; +using Strata.Id.Client.DTO; +using Strata.Licenses.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ClientLicense +{ + public class ClientLicenseService : IClientLicenseService + { + private readonly ILicensesServiceClient _licensesService; + private readonly IIdServiceClient _idServiceClient; + private readonly DatabaseRegexSection _databaseRegex; + + // Well-known CI license GUID + private static readonly Guid _ciLicenseGuid = Guid.Parse("feb1a208-693c-43fc-bdff-306bdfcdaaa4"); + + public ClientLicenseService( + ILicensesServiceClient licensesService, + IIdServiceClient idServiceClient, + IOptions sectionAccessor) + { + _licensesService = licensesService; + _idServiceClient = idServiceClient; + _databaseRegex = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(DatabaseRegexSection)); + } + + public async Task> 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); + + // Filter clients based on standard criteria + licensed status + return 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(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ClientLicense/IClientLicenseService.cs b/src/Strata.ContinuousImprovement.Biz/ClientLicense/IClientLicenseService.cs new file mode 100644 index 0000000..fb3ca3e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ClientLicense/IClientLicenseService.cs @@ -0,0 +1,12 @@ +using Strata.Id.Client.DTO; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ClientLicense +{ + public interface IClientLicenseService + { + Task> GetEligibleCIClientsAsync(bool isLicensed, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ClientWarehouses/ClientWarehouse.cs b/src/Strata.ContinuousImprovement.Biz/ClientWarehouses/ClientWarehouse.cs new file mode 100644 index 0000000..6874b26 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ClientWarehouses/ClientWarehouse.cs @@ -0,0 +1,20 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.ClientWarehouses +{ + public class ClientWarehouse + { + public int ClientWarehouseId { get; set; } + public Guid DatabaseGuid { get; set; } + public string ClientName { get; set; } + public WarehouseSize WarehouseSize { get; set; } = WarehouseSize.S; // Default to Small + } + + public enum WarehouseSize + { + S = 1, + M = 2, + L = 3, + XL = 4 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ClientWarehouses/ClientWarehouseService.cs b/src/Strata.ContinuousImprovement.Biz/ClientWarehouses/ClientWarehouseService.cs new file mode 100644 index 0000000..eb328b0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ClientWarehouses/ClientWarehouseService.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ClientWarehouses +{ + public class ClientWarehouseService : IClientWarehouseService + { + private readonly CentralDbContext _centralDbContext; + + public ClientWarehouseService(CentralDbContext centralDbContext) + { + _centralDbContext = centralDbContext; + } + + public async Task> GetAll(CancellationToken cancellationToken) + { + return await _centralDbContext.ClientWarehouses.ToListAsync(cancellationToken); + } + + public async Task GetByDatabaseGuid(Guid databaseGuid, CancellationToken cancellationToken) + { + return await _centralDbContext.ClientWarehouses.SingleOrDefaultAsync(x => x.DatabaseGuid == databaseGuid, cancellationToken); + } + + public async Task Add(ClientWarehouse clientWarehouse, CancellationToken cancellationToken) + { + await _centralDbContext.ClientWarehouses.AddAsync(clientWarehouse, cancellationToken); + await _centralDbContext.SaveChangesAsync(cancellationToken); + return clientWarehouse.ClientWarehouseId; + } + + public async Task Exists(Guid databaseGuid, CancellationToken cancellationToken) + { + return await _centralDbContext.ClientWarehouses.AnyAsync(x => x.DatabaseGuid == databaseGuid, cancellationToken: cancellationToken); + } + + public async Task Delete(Guid databaseGuid, CancellationToken cancellationToken) + { + var clientWarehouse = await _centralDbContext.ClientWarehouses.FirstAsync(x => x.DatabaseGuid == databaseGuid, cancellationToken); + _centralDbContext.ClientWarehouses.Remove(clientWarehouse); + var result = await _centralDbContext.SaveChangesAsync(cancellationToken); + return result > 0; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ClientWarehouses/IClientWarehouseService.cs b/src/Strata.ContinuousImprovement.Biz/ClientWarehouses/IClientWarehouseService.cs new file mode 100644 index 0000000..32ffdb1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ClientWarehouses/IClientWarehouseService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ClientWarehouses +{ + public interface IClientWarehouseService + { + Task Exists(Guid databaseGuid, CancellationToken cancellationToken); + Task> GetAll(CancellationToken cancellationToken); + Task GetByDatabaseGuid(Guid databaseGuid, CancellationToken cancellationToken); + Task Add(ClientWarehouse clientWarehouse, CancellationToken cancellationToken); + Task Delete(Guid databaseGuid, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Configuration/Configuration.cs b/src/Strata.ContinuousImprovement.Biz/Configuration/Configuration.cs new file mode 100644 index 0000000..582fb7b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configuration/Configuration.cs @@ -0,0 +1,329 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Score; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Configuration +{ + [Table("Configuration", Schema = "cci")] + public class Configuration + { + + [Key] + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public byte DisplayOrder { get; set; } + public Guid ServiceLineDimensionGuid { get; set; } + public string EntityIdsCSV { get; set; } + public bool IsUsingIndirectCost { get; set; } + public string DepartmentRollupGlobalId { get; set; } + public bool IsUsingCustomEntityDimension => !string.IsNullOrEmpty(DepartmentRollupGlobalId); + + public string CustomEntityIdsCSV { get; set; } + public QVIOpportunityBreakdowns QVIOpportunityBreakdown { get; set; } + public short FiscalYearID { get; set; } + public DateTime VariationEndDate { get; set; } + public DateTime QualityVariationEndDate { get; set; } + public DateTime OrgDefinedEndDate { get; set; } + public DateTime OrgDefinedTrackingCurrentMonth { get; set; } + public DateTime VariationTrackingCurrentMonth { get; set; } + public bool QualityVariationIsUsingAPRDRG { get; set; } + private IEnumerable EntityIds => EntityIdsCSV.SplitCSV(); + public string SourceSystemIdsCSV { get; set; } + public DateTime QualityVariationTrackingCurrentMonth { get; set; } + + private IEnumerable CustomEntityIds => CustomEntityIdsCSV.SplitCSV(); + + public IEnumerable ConfiguredEntityIds => IsUsingCustomEntityDimension ? CustomEntityIds : EntityIds; + + public int QVIOpportunityBreakdownEnum => (int)QVIOpportunityBreakdown; + + public string QVIOpportunityBreakdownName + { + get + { + switch (QVIOpportunityBreakdown) + { + case QVIOpportunityBreakdowns.Entity: + return "Entity"; + case QVIOpportunityBreakdowns.PhysicianSpecialty: + return "Physician Specialty"; + case QVIOpportunityBreakdowns.ServiceLine: + return "Service Line"; + default: + return "Breakdown"; + } + } + } + + public string QVIOpportunityBreakdownNamePlural + { + get + { + switch (QVIOpportunityBreakdown) + { + case QVIOpportunityBreakdowns.Entity: + return "Entities"; + case QVIOpportunityBreakdowns.PhysicianSpecialty: + return "Physician Specialties"; + case QVIOpportunityBreakdowns.ServiceLine: + return "Service Lines"; + default: + return "Breakdowns"; + } + } + } + + public string GetCaseTypeFamilyIdSqlColumn() => + QualityVariationIsUsingAPRDRG ? "APRDRGCaseTypeFamilyID" : "MSDRGCaseTypeFamilyID"; + + public IList GetSourceSystemIds() + { + return SourceSystemIdsCSV.ParseIntCsv(); + } + + public IList GetConfiguredEntityIds() + { + if (IsUsingCustomEntityDimension) + { + return CustomEntityIdsCSV.ParseIntCsv(); + } + return EntityIdsCSV.ParseIntCsv(); + } + + // ReSharper disable once InconsistentNaming + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public enum QVIOpportunityBreakdowns + { + Entity = 0, + ServiceLine = 1, + PhysicianSpecialty = 2 + } + + #region DateTime Methods + + [NotMapped] + public IFiscalMonthResolver FiscalMonthResolver { get; set; } + + [NotMapped] + public DateTime OrgDefinedTrackingStartDate { get; set; } + [NotMapped] + public DateTime OrgDefinedTrackingEndDate { get; set; } + [NotMapped] + public DateTime LastFiscalYearStartDate { get; set; } + [NotMapped] + public DateTime LastFiscalYearEndDate { get; set; } + + /// + /// Gets the baseline start date by opportunity type (variation/qualityvariation/other) + /// + public DateTime GetBaselineStartDate(OpportunityTypes opportunityType) + => GetBaselineEndDate(opportunityType).GetStartOrEndDateOfYearPeriod(-1); + + /// + /// Adjust Baseline End Date to the beginning of the next day + /// + /// + /// + public DateTime BaselineEndDate(OpportunityTypes opportunityType) + => GetBaselineEndDate(opportunityType).AddDays(1).Date; + + /// + /// Gets the baseline end date by opportunity type (variation/qualityvariation/other) + /// + public DateTime GetBaselineEndDate(OpportunityTypes opportunityType) + { + DateTime endDate; + + switch (opportunityType) + { + case OpportunityTypes.Variation: + case OpportunityTypes.Exploration: + endDate = VariationEndDate; + break; + case OpportunityTypes.QualityVariation: + endDate = QualityVariationEndDate; + break; + case OpportunityTypes.OrgDefined: + case OpportunityTypes.FreeForm: + case OpportunityTypes.GeneralLedger: + case OpportunityTypes.Payroll: + case OpportunityTypes.Encounter: + case OpportunityTypes.ChargeCode: + endDate = OrgDefinedEndDate; + break; + default: + throw new InvalidEnumArgumentException(); + } + + return endDate; + } + + /// + /// This needs to be updated with the use of the Fiscal Year + /// + /// + /// + public DateTime GetTrackingStartDate(OpportunityTypes opportunityType) + { + var baselineEndDate = GetBaselineEndDate(opportunityType); + + baselineEndDate = new DateTime(baselineEndDate.Year, baselineEndDate.Month, 1); + + var fiscalYearStartDate = GetFiscalYearStartDate(); + + while (baselineEndDate >= fiscalYearStartDate) + { + fiscalYearStartDate = fiscalYearStartDate.AddMonths(1); + } + + return fiscalYearStartDate; + } + + public DateTime GetTrackingEndDate() => GetFiscalYearEndDate(); + + /// + /// This will need to updated when we have the FiscalMonthResolver + /// + /// + public DateTime GetFiscalYearEndDate() + { + var lastFiscalMonth = FiscalMonthResolver.LastFiscalMonth().FiscalMonthId; + return new DateTime(FiscalYearID, lastFiscalMonth, DateTime.DaysInMonth(FiscalYearID, lastFiscalMonth)); + } + + public DateTime GetLastFiscalYearEndDate() + { + var lastFiscalMonth = FiscalMonthResolver.LastFiscalMonth().FiscalMonthId; + short lastFiscalYearID = (short)(FiscalYearID - 1); + return new DateTime(lastFiscalYearID, lastFiscalMonth, DateTime.DaysInMonth(lastFiscalYearID, lastFiscalMonth)); + } + + public DateTime GetLastFiscalYearStartDate() + { + return GetLastFiscalYearEndDate().GetStartOrEndDateOfYearPeriod(-1); + } + + public DateTime GetFiscalYearStartDate() + { + return GetFiscalYearEndDate().GetStartOrEndDateOfYearPeriod(-1); + } + + public DateTime GetTrackingCurrentMonthEndDate(OpportunityTypes opportunityType) + { + var startDate = GetTrackingCurrentMonthStartDate(opportunityType); + return startDate.AddMonths(1).AddDays(-1); + } + + /// + /// Adjust Tracking Current Month End Date to the beginning of the next day + /// + /// + /// Tracking Current Month End Date at Midnight of the next day + public DateTime TrackingCurrentMonthEndDate(OpportunityTypes opportunityType) + => GetTrackingCurrentMonthEndDate(opportunityType).AddDays(1).Date; + + + public DateTime GetTrackingCurrentMonthStartDate(OpportunityTypes opportunityType) + { + var currentMonth = GetTrackingCurrentMonth(opportunityType); + return new DateTime(currentMonth.Year, currentMonth.Month, 1); + } + + private DateTime GetTrackingCurrentMonth(OpportunityTypes opportunityType) + { + DateTime date; + + switch (opportunityType) + { + case OpportunityTypes.ChargeCode: + date = OrgDefinedTrackingCurrentMonth; + break; + case OpportunityTypes.QualityVariation: + date = QualityVariationTrackingCurrentMonth; + break; + default: + date = VariationTrackingCurrentMonth; + break; + } + + return date; + } + + public IList GetTrackingFiscalMonths(OpportunityTypes opportunityType, DateTime effectiveDate) + { + var calendarStartDate = GetFiscalYearStartDate(); + var calendarEndDate = GetFiscalYearEndDate(); + + var fiscalMonth = FiscalMonthResolver.FirstFiscalMonth(); + var sortOrder = 1; + + var currentTrackingDate = GetTrackingCurrentMonthStartDate(opportunityType); + + var trackingMonths = new List(); + + while (calendarStartDate <= calendarEndDate) + { + var isAfterEffectiveDate = calendarStartDate >= effectiveDate; + var isBeforeTrackingDate = calendarStartDate <= currentTrackingDate; + + trackingMonths.Add(new TrackingMonth(fiscalMonth.Abbreviation, isAfterEffectiveDate, + isBeforeTrackingDate, calendarStartDate.Month == currentTrackingDate.Month)); + + calendarStartDate = calendarStartDate.AddMonths(1); + sortOrder++; + if (sortOrder <= 12) fiscalMonth = FiscalMonthResolver.GetByFiscalMonthSortOrder(sortOrder); + } + + return trackingMonths; + } + + [NotMapped] + public ScoreDimension ServiceLineDimension { get; set; } + + [NotMapped] + public ScoreAttribute ServiceLineAttribute { get; set; } + + + public async Task GetServiceLineDimension(JazzDbContext jazzDbContext, CancellationToken cancellationToken) + { + if (ServiceLineDimension != null) { return ServiceLineDimension; } + ScoreDimension scoreDimension = default; + if (jazzDbContext != null) + { + scoreDimension = await jazzDbContext.ScoreDimensions + .FirstOrDefaultAsync(x => x.DimensionGuid == ServiceLineDimensionGuid, cancellationToken); + if (scoreDimension == null) + { + scoreDimension = await jazzDbContext.ScoreDimensions + .FirstOrDefaultAsync(x => x.GlobalId == Dimension.ServiceLine, cancellationToken); + } + ServiceLineDimension = scoreDimension; + } + return scoreDimension; + } + + public async Task GetServiceLineAttribute(JazzDbContext jazzDbContext, CancellationToken cancellationToken) + { + if (ServiceLineAttribute != null) { return ServiceLineAttribute; } + var scoreDimension = await GetServiceLineDimension(jazzDbContext, cancellationToken); + var attributeGuid = scoreDimension?.DefaultKeyGuid; + var scoreAttribute = await jazzDbContext?.ScoreAttributes.FirstOrDefaultAsync(x => x.AttributeGuid == attributeGuid, cancellationToken); + ServiceLineAttribute = scoreAttribute; + return scoreAttribute; + } + + #endregion + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Configuration/ConfigurationService.cs b/src/Strata.ContinuousImprovement.Biz/Configuration/ConfigurationService.cs new file mode 100644 index 0000000..1cb4b6b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configuration/ConfigurationService.cs @@ -0,0 +1,92 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Configuration +{ + public class ConfigurationService : Generics.JazzDbService, IConfigurationService + { + + private readonly ILogger _logger; + private readonly IFiscalMonthResolver _fiscalMonthResolver; + + public ConfigurationService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, IClaimsPrincipalAccessor claimsPrincipalAccessor, + ILogger logger, + IFiscalMonthResolver fiscalMonthResolver) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor) + { + _logger = logger; + _fiscalMonthResolver = fiscalMonthResolver; + } + + public async Task> GetConfigurationsAsync(CancellationToken cancellationToken) + { + try + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext is null) return default; + var configurations = await dbContext.Configurations.ToListAsync(cancellationToken); + configurations.ForEach(config => SetCalculatedConfigurationDates(config)); + return configurations; + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + throw; + } + } + + public async Task GetDefaultConfigurationAsync(CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext is null) return default; + var configuration = await dbContext.Set() + .OrderBy(config => config.DisplayOrder).FirstOrDefaultAsync(cancellationToken); + SetCalculatedConfigurationDates(configuration); + return configuration; + } + + public async Task GetConfigurationAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext is null) return default; + var configuration = await dbContext.Configurations.FirstOrDefaultAsync(config => config.ConfigurationGuid == configurationGuid, cancellationToken); + if (configuration is null) return default; + SetCalculatedConfigurationDates(configuration); + return configuration; + } + + public async Task GetQualityVariationAprdrgFlagAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + var config = await GetConfigurationAsync(configurationGuid, cancellationToken); + return config?.QualityVariationIsUsingAPRDRG ?? false; + } + + private void SetCalculatedConfigurationDates(Configuration configuration) + { + configuration.FiscalMonthResolver = _fiscalMonthResolver; + configuration.OrgDefinedTrackingStartDate = configuration.GetTrackingStartDate(OpportunityTypes.OrgDefined); + configuration.OrgDefinedTrackingEndDate = configuration.GetTrackingEndDate(); + configuration.LastFiscalYearStartDate = configuration.GetLastFiscalYearStartDate(); + configuration.LastFiscalYearEndDate = configuration.GetLastFiscalYearEndDate(); + } + + public async Task IsConfigurationValid(Guid configurationGuid, CancellationToken cancellationToken) + { + var configuration = new Configuration { ConfigurationGuid = configurationGuid }; + return await this.Contains(configuration, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Configuration/IConfigurationService.cs b/src/Strata.ContinuousImprovement.Biz/Configuration/IConfigurationService.cs new file mode 100644 index 0000000..283b5d5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configuration/IConfigurationService.cs @@ -0,0 +1,17 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Configuration +{ + public interface IConfigurationService : IJazzDbService + { + Task> GetConfigurationsAsync(CancellationToken cancellationToken); + Task GetDefaultConfigurationAsync(CancellationToken cancellationToken); + Task GetConfigurationAsync(Guid configurationGuid, CancellationToken cancellationToken); + Task GetQualityVariationAprdrgFlagAsync(Guid configurationGuid, CancellationToken cancellationToken); + Task IsConfigurationValid(Guid configurationGuid, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Configuration/Member.cs b/src/Strata.ContinuousImprovement.Biz/Configuration/Member.cs new file mode 100644 index 0000000..727dc43 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configuration/Member.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.Configuration +{ + public class Member + { + public int Id { get; set; } + + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Configurations/Constants.cs b/src/Strata.ContinuousImprovement.Biz/Configurations/Constants.cs new file mode 100644 index 0000000..8a64996 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configurations/Constants.cs @@ -0,0 +1,7 @@ +namespace Strata.ContinuousImprovement.Biz.Configurations +{ + internal static class Constants + { + internal static readonly string ApplicationName = "continuous-improvement"; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Configurations/ContinuousImprovementServiceExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Configurations/ContinuousImprovementServiceExtensions.cs new file mode 100644 index 0000000..6325d97 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configurations/ContinuousImprovementServiceExtensions.cs @@ -0,0 +1,359 @@ +using Amazon.Extensions.NETCore.Setup; +using Dapper; +using Hangfire; +using Hangfire.Throttling; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.BackgroundClientJob; +using Strata.ContinuousImprovement.Biz.ChargeCode; +using Strata.ContinuousImprovement.Biz.ChargeCode.Initiatives; +using Strata.ContinuousImprovement.Biz.ChargeCode.Jobs; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.ClientLicense; +using Strata.ContinuousImprovement.Biz.ClientWarehouses; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.Cube; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Dapper; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Email; +using Strata.ContinuousImprovement.Biz.Encounter.Initiatives; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.Exploration; +using Strata.ContinuousImprovement.Biz.Exploration.Opportunities; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives; +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Details; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Initiatives; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.GMDN; +using Strata.ContinuousImprovement.Biz.GMDN.Jobs; +using Strata.ContinuousImprovement.Biz.Hangfire; +using Strata.ContinuousImprovement.Biz.InitiativeDetailColumns; +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.MappedColumns; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.OpportunityComments; +using Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment; +using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks; +using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks; +using Strata.ContinuousImprovement.Biz.OrgDefined; +using Strata.ContinuousImprovement.Biz.Payroll.Initiatives; +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities; +using Strata.ContinuousImprovement.Biz.PeerGroups; +using Strata.ContinuousImprovement.Biz.PickerItems; +using Strata.ContinuousImprovement.Biz.Population; +using Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns; +using Strata.ContinuousImprovement.Biz.QualityVariation.Events; +using Strata.ContinuousImprovement.Biz.QualityVariation.Jobs; +using Strata.ContinuousImprovement.Biz.QualityVariation.Metrics; +using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities; +using Strata.ContinuousImprovement.Biz.QualityVariation.QVI; +using Strata.ContinuousImprovement.Biz.Replication; +using Strata.ContinuousImprovement.Biz.Reports; +using Strata.ContinuousImprovement.Biz.Security; +using Strata.ContinuousImprovement.Biz.Settings; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Email; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries; +using Strata.ContinuousImprovement.Biz.UserService; +using Strata.ContinuousImprovement.Biz.UserSetting; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.ContinuousImprovement.Biz.UtilizationVariation; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.Hangfire.Configuration; +using Strata.Id.Client; +using Strata.Replication.Client; +using Strata.SnowflakeLib.Configuration; +using Strata.SqlTools.Configuration.Postgres; +using Strata.SqlTools.Configuration.SqlServer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using ConnectionStringBuilder = Strata.SqlTools.Configuration.Postgres.ConnectionStringBuilder; + +// ReSharper disable once CheckNamespace + +namespace Strata.ContinuousImprovement.Biz.Configurations +{ + [ExcludeFromCodeCoverage] + public static class ContinuousImprovementServiceExtensions + { + private const string CacheProviderName = "continuousImprovement_cache"; + const int commandTimeoutInSeconds = 300; + + public static IServiceCollection AddContinuousImprovementServices(this IServiceCollection services, IConfiguration configuration, + string integrationTesting = null) + { + if (services == null) + throw new ArgumentNullException(nameof(services)); + services.AddProblemDetails(options => + { + options.CustomizeProblemDetails = context => + { + context.ProblemDetails.Title = context.ProblemDetails.Title ?? context.ProblemDetails.Status.ProblemDetailTitle(); + context.ProblemDetails.Type = context.ProblemDetails.Type ?? context.ProblemDetails.Status.ProblemDetailType(); + context.ProblemDetails.Instance = $"{context.HttpContext.Request.Method} {context.HttpContext.Request.Path}"; + context.ProblemDetails.Extensions.TryAdd("requestId", context.HttpContext.TraceIdentifier); + var activity = context.HttpContext.Features.Get()?.Activity; + activity?.Baggage?.ForEach(b => context.ProblemDetails.Extensions.TryAdd(b.Key, b.Value)); + var exceptionData = context.Exception?.Data; + if (exceptionData != null) + { + foreach (DictionaryEntry entry in exceptionData) + { + context.ProblemDetails.Extensions.TryAdd(entry.Key.ToString(), entry.Value); + } + } + }; + }); + + services.AddAuthorizationServiceClient(); + + services.AddOptions().BindConfiguration("aws").ValidateDataAnnotations(); //bind the options for aws and validates for missing values + + //Important step for In-Memory Caching + services.AddEasyCaching(options => + { + // use memory cache with your own configuration + options.UseInMemory(config => + { + config.EnableLogging = true; + }, CacheProviderName); + }); + + //hangfire + services.AddTransient(); + services.AddTransient(); + services.ConfigureHangfireOptionsFromAws(options => + { + options.Schema = Constants.ApplicationName; + }); + + services.AddSignalR(); + services.AddScoped(); + services.AddSchemaServiceClient(); + services.AddDataSchemaService(); + services.AddUsersServiceClient(); + services.AddLicensesServiceClient(); + services.AddJazzHangfireServiceClient(); + services.AddFeatureFlagsServiceClient(); + services.AddScoped(); + services.AddIdServiceClient(); + services.AddAnalyticsService(); + services.AddReplicationServiceClient(); + services.AddStrataSphereCompareServiceClient(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddTransient(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + //bind the options for aws and validates for missing values + services.AddOptions().BindConfiguration("NetworkOpportunity").ValidateDataAnnotations(); + + services.AddSchemaServiceClient(); + services.AddUsersServiceClient(); + services.AddJazzHangfireServiceClient(); + + //strata service clients + services.AddIdServiceClient(); + services.AddCachedSmcServiceClient(); + + services.AddHttpContextAccessor(); + services.AddClaimsPrincipalAccessor(); + + services.AddOptions().BindConfiguration("Hangfire").ValidateDataAnnotations(); + services.AddOptions().BindConfiguration("Datadog").ValidateDataAnnotations(); + services.AddOptions().BindConfiguration("AccessGudid").ValidateDataAnnotations(); + services.AddOptions().BindConfiguration("GMDNFTP").ValidateDataAnnotations(); + services.AddOptions().BindConfiguration("ChargeCodeOpportunityValidation").ValidateDataAnnotations(); + services.AddOptions().BindConfiguration("Databases").ValidateDataAnnotations(); + + + services.AddTransient>(provider => + { + return new OptionsWrapper(new MailGunOption + { + ApiKey = configuration.GetValue("mailgun:ApiKey"), + BaseUrl = configuration.GetValue("mailgun:BaseUrl"), + Domain = configuration.GetValue("mailgun:Domain"), + FromEmailAddress = configuration.GetValue("mailgun:FromEmailAddress") + }); + }); + + //Postgres + var connectionString = string.IsNullOrEmpty(integrationTesting) + ? ConnectionStringBuilder.Build(configuration, "continuousimprovement") + : ConnectionStringBuilder.Build(configuration, integrationTesting); + + services.AddSnowflakeServices(configuration); + services.AddDapperServices(configuration); + + services.AddPostgres((options, provider) => + { + options.ConnectionString = connectionString; + }); + + if (!string.IsNullOrEmpty(integrationTesting)) return services; + + services.AddAsyncDbContextFactory(options => + { + options + .WithConnectionString((provider, token) => provider.GetConnectionStringFromSmc(token)) + .WithDbContextOptions((connectionString, provider, builder) => + builder.UseSqlServer(connectionString, + options => { options.CommandTimeout(commandTimeoutInSeconds); })); + }); + return services; + } + + private static void AddSnowflakeServices(this IServiceCollection services, IConfiguration configuration) + { + services.AddSnowflake(configuration); + services.AddScoped(); + services.AddScoped(provider => + { + if (!IsDatabaseGuidKnown(provider)) + { + return null; + } + + ISnowflakeDatabaseContextFactory snowflakeDatabaseContextFactory = provider.GetRequiredService(); + IClientWarehouseService clientWarehouseService = provider.GetRequiredService(); + return snowflakeDatabaseContextFactory.Create(provider.GetRequiredService().GetCurrentClaimsPrincipal()?.GetDatabaseGuid().ToString("N")); + }); + } + + private static void AddDapperServices(this IServiceCollection services, IConfiguration configuration) + { + SqlMapper.AddTypeHandler(new DateOnlyTypeHandler()); + } + + private static bool IsDatabaseGuidKnown(IServiceProvider provider) + { + try + { + return provider.GetRequiredService().GetCurrentClaimsPrincipal()?.GetDatabaseGuid() != null; + } + catch (ClaimNotFoundException) + { + return false; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Configurations/Hangfire/CaptureJobParameterAttribute.cs b/src/Strata.ContinuousImprovement.Biz/Configurations/Hangfire/CaptureJobParameterAttribute.cs new file mode 100644 index 0000000..66a5367 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configurations/Hangfire/CaptureJobParameterAttribute.cs @@ -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.ContinuousImprovement.Biz.Configurations.Hangfire +{ + /// + /// Captures Job parameters as Claims on the ThreadPrincipal running the job + /// + [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(); + 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; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Configurations/Hangfire/DeleteOnSuccessAttribute.cs b/src/Strata.ContinuousImprovement.Biz/Configurations/Hangfire/DeleteOnSuccessAttribute.cs new file mode 100644 index 0000000..c121241 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configurations/Hangfire/DeleteOnSuccessAttribute.cs @@ -0,0 +1,16 @@ +using Hangfire.Common; +using Hangfire.States; + +namespace Strata.ContinuousImprovement.Biz.Configurations.Hangfire +{ + public class DeleteOnSuccessAttribute : JobFilterAttribute, IElectStateFilter + { + public void OnStateElection(ElectStateContext context) + { + if (context.CandidateState.Name == SucceededState.StateName) + { + context.CandidateState = new DeletedState { Reason = "This job is deleted on success." }; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Configurations/Hangfire/HangfireEndpointRouteBuilderExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Configurations/Hangfire/HangfireEndpointRouteBuilderExtensions.cs new file mode 100644 index 0000000..d993304 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configurations/Hangfire/HangfireEndpointRouteBuilderExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Strata.ContinuousImprovement.Biz.Hubs; + +namespace Strata.ContinuousImprovement.Biz.Configurations.Hangfire +{ + public static class HangfireEndpointRouteBuilderExtensions + { + public static HubEndpointConventionBuilder MapHangfireHub(this IEndpointRouteBuilder endpoints) + { + return endpoints.MapHub(HangfireHub.HubURL, configureOptions: null) + .RequireAuthorization(); + } + } +} + diff --git a/src/Strata.ContinuousImprovement.Biz/Configurations/HubEndpointRouteBuilderExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Configurations/HubEndpointRouteBuilderExtensions.cs new file mode 100644 index 0000000..1ea86fe --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configurations/HubEndpointRouteBuilderExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Strata.ContinuousImprovement.Biz.Notification; + +namespace Strata.ContinuousImprovement.Biz.Configurations +{ + public static class HubEndpointRouteBuilderExtensions + { + public static HubEndpointConventionBuilder MapNotificationHub(this IEndpointRouteBuilder endpoints) + { + return endpoints.MapHub("/api/NotificationHub", configureOptions: null) + .RequireAuthorization(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Configurations/QueryRecursionSection.cs b/src/Strata.ContinuousImprovement.Biz/Configurations/QueryRecursionSection.cs new file mode 100644 index 0000000..07ca815 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Configurations/QueryRecursionSection.cs @@ -0,0 +1,14 @@ +namespace Strata.ContinuousImprovement.Biz.Configurations +{ + public class QueryRecursionSection + { + public RecursionInfo CCOpportunityDetailQuery { get; set; } + } + + public class RecursionInfo + { + public int Start { get; set; } + public int End { get; set; } + public int Increment { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ContinuousImprovementBootstrapperService.cs b/src/Strata.ContinuousImprovement.Biz/ContinuousImprovementBootstrapperService.cs new file mode 100644 index 0000000..3c32416 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ContinuousImprovementBootstrapperService.cs @@ -0,0 +1,182 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.ChargeCode.Jobs; +using Strata.ContinuousImprovement.Biz.Encounter; +using Strata.ContinuousImprovement.Biz.GMDN.Jobs; +using Strata.ContinuousImprovement.Biz.Hangfire; +using Strata.ContinuousImprovement.Biz.Population; +using Strata.ContinuousImprovement.Biz.QualityVariation.Jobs; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz +{ + public class ContinuousImprovementBootstrapperService : BackgroundService + { + private readonly ILogger _logger; + private readonly IHostEnvironment _environment; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly IRecurringJobManager _recurringJobManager; + public IThrottlingManager _throttlingManager { get; } + + public ContinuousImprovementBootstrapperService(ILogger logger, + IHostEnvironment environment, + IRecurringJobManager recurringJobManager, + IBackgroundJobClient backgroundJobClient, + IThrottlingManager throttlingManager) + { + _logger = logger; + _environment = environment; + _recurringJobManager = recurringJobManager; + _backgroundJobClient = backgroundJobClient; + _throttlingManager = throttlingManager; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var jobOptions = new RecurringJobOptions() { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time") }; + + var cronOnDemand = Cron.Never; // Never run job, 31st of Feb + + // the schedule is set for every day at 9:05pm + var cronExpr = Cron.Daily(21, 5); + + // the schedule is set for 1st of every Month at 12:00am + var cronMonthlyExpr = Cron.Monthly(1); + + // the schedule is set for 3rd of every Month at 12:00am + var cronThirdDayOfMonthExpr = Cron.Monthly(3); + + _throttlingManager.AddOrUpdateSemaphore(nameof(CCOpportunityDetailJob.PopulateOpportunityBaselineDetailsAsync), new SemaphoreOptions(maxCount: 10)); + _throttlingManager.AddOrUpdateSemaphore(nameof(CCOpportunityDetailJob.PopulateOpportunityTrackingDetailsAsync), new SemaphoreOptions(maxCount: 10)); + _throttlingManager.AddOrUpdateSemaphore(nameof(CCOpportunityDetailJob), new SemaphoreOptions(maxCount: 10)); + _throttlingManager.AddOrUpdateSemaphore(nameof(QviOpportunityMetricsJob.PopulateQviMetricsAsync), new SemaphoreOptions(maxCount: 10)); + _throttlingManager.AddOrUpdateSemaphore(nameof(ENOpportunityDetailJob.PopulateOpportunityDetailsAsync), new SemaphoreOptions(maxCount: 10)); + + _throttlingManager.AddOrUpdateSemaphore(nameof(SeedGUDIDIdentifierDataJob.MergeGUDIDIdentifierData), new SemaphoreOptions(maxCount: 1)); + + _throttlingManager.AddOrUpdateSemaphore(nameof(GUDIDDeviceDailyUpdateJob.GUDIDDeviceDailyUpdate), new SemaphoreOptions(maxCount: 1)); + _throttlingManager.AddOrUpdateSemaphore(nameof(GUDIDDeviceDailyUpdateJob.GUDIDDeviceDailyPageUpdate), new SemaphoreOptions(maxCount: 10)); + _throttlingManager.AddOrUpdateSemaphore(nameof(GMDNMonthlyUpdateJob.RefreshGMDNData), new SemaphoreOptions(maxCount: 1)); + _throttlingManager.AddOrUpdateSemaphore(nameof(PrimaryMeasureMonitoringJob.RunNotifyPrimaryMeasureMonitoring), new SemaphoreOptions(maxCount: 10)); + _throttlingManager.AddOrUpdateSemaphore(nameof(SeedDeviceIdentifierDimensionJob.SeedDeviceIdentifierDimension), new SemaphoreOptions(maxCount: 1)); + _throttlingManager.AddOrUpdateSemaphore(nameof(SeedDeviceIdentifierDimensionJob.SeedDeviceIdentifierDimensionBatch), new SemaphoreOptions(maxCount: 3)); + _throttlingManager.AddOrUpdateSemaphore(nameof(DeviceIdentifierClientMappingJob.MapClientDimDeviceIdentifier), new SemaphoreOptions(maxCount: 10)); + _throttlingManager.AddOrUpdateSemaphore(nameof(DeviceIdentifierClientClearingJob.ClearClientDimDeviceIdentifier), new SemaphoreOptions(maxCount: 10)); + _throttlingManager.AddOrUpdateSemaphore(nameof(ExplorationPopulationReplicationJob.ReplicateAllClientExplorationPopulations), new SemaphoreOptions(maxCount: 1)); + +#if DEBUG + //disable our scheduled tasks if we're debugging + cronExpr = Cron.Never(); + cronMonthlyExpr = Cron.Never(); +#endif + + try + { + // Daily Jobs + _recurringJobManager.AddOrUpdate(nameof(GUDIDDeviceDailyUpdateJob), + job => job.GUDIDDeviceDailyUpdate(CancellationToken.None), cronExpr, jobOptions); + + _recurringJobManager.AddOrUpdate(nameof(DeviceIdentifierMappingJob), + job => job.MapDimDeviceIdentifierToJazz(CancellationToken.None), cronExpr, jobOptions); + + _recurringJobManager.AddOrUpdate(nameof(ExplorationPopulationReplicationJob), + job => job.ReplicateAllClientExplorationPopulations(CancellationToken.None), cronExpr, jobOptions); + + // Monthly Jobs + _recurringJobManager.AddOrUpdate(nameof(GMDNMonthlyUpdateJob), + job => job.RefreshGMDNData(CancellationToken.None), cronMonthlyExpr, jobOptions); + + _recurringJobManager.AddOrUpdate(nameof(PrimaryMeasureMonitoringJob), + job => job.RunNotifyPrimaryMeasureMonitoring(CancellationToken.None), cronThirdDayOfMonthExpr, jobOptions); + + // On Demand Jobs + _recurringJobManager.AddOrUpdate(nameof(SeedDeviceIdentifierDimensionJob), + job => job.SeedDeviceIdentifierDimension(CancellationToken.None), cronOnDemand, jobOptions); + + _recurringJobManager.AddOrUpdate(nameof(DeviceIdentifierClearingJob), + job => job.ClearDimDeviceIdentifierFromJazz(CancellationToken.None), cronOnDemand, jobOptions); + + // Removing outdated jobs explicitly -- this can be removed once code is deployed and job is removed from recurring job manager + _recurringJobManager.RemoveIfExists("GMDNMappingJob"); + _recurringJobManager.RemoveIfExists("PrimaryDIMappingJob"); + _recurringJobManager.RemoveIfExists("SeedDimPrimaryDIJob"); + _recurringJobManager.RemoveIfExists("SeedGMDNTermDataJob"); + _recurringJobManager.RemoveIfExists("SeedDimDeviceIdentifierJob"); + _recurringJobManager.RemoveIfExists("UpdateFactOpportunitySavingsJob"); + _recurringJobManager.RemoveIfExists("CCOpportunityJob"); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + } + + if (!_environment.IsDevelopment()) + { + _recurringJobManager.RemoveIfExists(nameof(SprintRefresh.SprintRefreshJob)); + _recurringJobManager.RemoveIfExists(nameof(SprintRefresh.CommentsRefreshJob)); + _recurringJobManager.RemoveIfExists(nameof(SprintRefresh.OpportunityWorkbooksRefreshJob)); + _recurringJobManager.RemoveIfExists(nameof(SprintRefresh.NetworkOpportunityRefreshJob)); + _recurringJobManager.RemoveIfExists(nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob) + "DevMaster"); + _recurringJobManager.RemoveIfExists(nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob) + "QAMaster"); + } + else + { + try + { + _recurringJobManager.RemoveIfExists(nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob)); + + _throttlingManager.AddOrUpdateSemaphore(nameof(SprintRefresh.SprintRefreshJob), new SemaphoreOptions(maxCount: 1)); + _throttlingManager.AddOrUpdateSemaphore(nameof(SprintRefresh.CommentsRefreshJob), new SemaphoreOptions(maxCount: 1)); + _throttlingManager.AddOrUpdateSemaphore(nameof(SprintRefresh.OpportunityWorkbooksRefreshJob), new SemaphoreOptions(maxCount: 1)); + _throttlingManager.AddOrUpdateSemaphore(nameof(SprintRefresh.NetworkOpportunityRefreshJob), new SemaphoreOptions(maxCount: 1)); + _throttlingManager.AddOrUpdateSemaphore(nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob), new SemaphoreOptions(maxCount: 10)); + _throttlingManager.AddOrUpdateSemaphore(nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob) + "DevMaster", new SemaphoreOptions(maxCount: 1)); + _throttlingManager.AddOrUpdateSemaphore(nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob) + "QAMaster", new SemaphoreOptions(maxCount: 1)); + _throttlingManager.AddOrUpdateSemaphore(nameof(HangfireUtility.RunJazzHangfireTaskJob), new SemaphoreOptions(maxCount: 10)); + + var sprintRefresh = nameof(SprintRefresh.SprintRefreshJob); + _recurringJobManager.AddOrUpdate(sprintRefresh, + job => job.SprintRefreshJob(CancellationToken.None), + cronOnDemand, new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); + + var commentsRefresh = nameof(SprintRefresh.CommentsRefreshJob); + _recurringJobManager.AddOrUpdate(commentsRefresh, + job => job.CommentsRefreshJob(CancellationToken.None), + cronOnDemand, new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); + + var opportunityWorkbookRefresh = nameof(SprintRefresh.OpportunityWorkbooksRefreshJob); + _recurringJobManager.AddOrUpdate(opportunityWorkbookRefresh, + job => job.OpportunityWorkbooksRefreshJob(CancellationToken.None), + cronOnDemand, new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); + + var networkOpportunityRefresh = nameof(SprintRefresh.NetworkOpportunityRefreshJob); + _recurringJobManager.AddOrUpdate(networkOpportunityRefresh, + job => job.NetworkOpportunityRefreshJob(CancellationToken.None), + cronOnDemand, new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); + + var runUpdateFactOpportunitySavingsRefreshDevMaster = nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob) + "DevMaster"; + _recurringJobManager.AddOrUpdate(runUpdateFactOpportunitySavingsRefreshDevMaster, + job => job.RunUpdateFactOpportunitySavingsTaskJob("Dev Master", CancellationToken.None), + cronOnDemand, new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); + + var runUpdateFactOpportunitySavingsRefreshQaMaster = nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob) + "QAMaster"; + _recurringJobManager.AddOrUpdate(runUpdateFactOpportunitySavingsRefreshQaMaster, + job => job.RunUpdateFactOpportunitySavingsTaskJob("QA Master", CancellationToken.None), + cronOnDemand, new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + } + } + await Task.CompletedTask; + } + } +} + diff --git a/src/Strata.ContinuousImprovement.Biz/Cube/CubeService.cs b/src/Strata.ContinuousImprovement.Biz/Cube/CubeService.cs new file mode 100644 index 0000000..7085163 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Cube/CubeService.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Cube +{ + public class CubeService : ICubeService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ILogger _logger; + private const string CostingProcessCubeLockGroup = "CostingProcessCubeTask"; + + public CubeService(IAsyncDbContextFactory dbContextFactory, ILogger logger) + { + _dbContextFactory = dbContextFactory; + _logger = logger; + } + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task IsCubeRunningAsync(CancellationToken cancellationToken) + { + try + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return false; + return await dbContext.Locks.AnyAsync(l => l.LockGroup == CostingProcessCubeLockGroup, cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error checking cube processing status"); + throw; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Cube/ICubeService.cs b/src/Strata.ContinuousImprovement.Biz/Cube/ICubeService.cs new file mode 100644 index 0000000..6a29fb6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Cube/ICubeService.cs @@ -0,0 +1,10 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Cube +{ + public interface ICubeService + { + Task IsCubeRunningAsync(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Cube/Lock.cs b/src/Strata.ContinuousImprovement.Biz/Cube/Lock.cs new file mode 100644 index 0000000..6e3c497 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Cube/Lock.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Cube +{ + [Table("Lock", Schema = "dbo")] + public class Lock + { + public Guid LockGUID { get; set; } + public Guid ItemGUID { get; set; } + public Guid UserGUID { get; set; } + public DateTime DateLocked { get; set; } + public string LockGroup { get; set; } + public DateTime DateLastActive { get; set; } + public string UserName { get; set; } + public DateTime DateLockedUtc { get; set; } + public DateTime DateLastActiveUtc { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Databases/Database.cs b/src/Strata.ContinuousImprovement.Biz/Databases/Database.cs new file mode 100644 index 0000000..378e986 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Databases/Database.cs @@ -0,0 +1,11 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Databases +{ + public class Database + { + public string DatabaseName { get; set; } + public Guid DatabaseGuid { get; set; } + public string OrgPin { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Databases/DatabaseService.cs b/src/Strata.ContinuousImprovement.Biz/Databases/DatabaseService.cs new file mode 100644 index 0000000..a157a38 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Databases/DatabaseService.cs @@ -0,0 +1,55 @@ +using Strata.ContinuousImprovement.Biz.Databases; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.Id.Client; +using Strata.Replication.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Replication +{ + public class DatabaseService : IDatabaseService + { + private readonly IReplicationServiceClient _replicationService; + private readonly IClaimsPrincipalAccessor _claimsPrincipalAccessor; + private readonly IIdServiceClient _idServiceClient; + + public DatabaseService(IReplicationServiceClient replicationServiceClient + , IClaimsPrincipalAccessor claimsPrincipalAccessor + , IIdServiceClient idServiceClient + ) + { + _replicationService = replicationServiceClient; + _claimsPrincipalAccessor = claimsPrincipalAccessor; + _idServiceClient = idServiceClient; + } + + public async Task IsClientOnboardedAsync(CancellationToken cancellationToken) + { + var user = _claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + var dbGuid = user?.GetDatabaseGuid() ?? Guid.Empty; + if (dbGuid == Guid.Empty) { return false; } + + var strataId = await _idServiceClient.GetStrataId(dbGuid); + return await _replicationService.IsOnboarded(strataId, cancellationToken); + } + + public async Task> GetDatabasesAsync(CancellationToken cancellationToken) + { + var clients = await _idServiceClient.GetStrataIds(cancellationToken); + var databases = clients.Select(d => new Database + { + DatabaseGuid = d.DatabaseGUID, + DatabaseName = d.DatabaseName, + OrgPin = d.OrgPin + }).OrderBy(db => db.OrgPin); + + return databases.ToList(); + } + + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Databases/IDatabaseService.cs b/src/Strata.ContinuousImprovement.Biz/Databases/IDatabaseService.cs new file mode 100644 index 0000000..c4f1ade --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Databases/IDatabaseService.cs @@ -0,0 +1,13 @@ +using Strata.ContinuousImprovement.Biz.Databases; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Replication +{ + public interface IDatabaseService + { + Task> GetDatabasesAsync(CancellationToken cancellationToken); + Task IsClientOnboardedAsync(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/CentralDbContext.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/CentralDbContext.cs new file mode 100644 index 0000000..532bca4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/CentralDbContext.cs @@ -0,0 +1,189 @@ +using Dapper; +using Microsoft.EntityFrameworkCore; +using Npgsql; +using Strata.Analytics.Models.Workbooks; +using Strata.ContinuousImprovement.Biz.ClientWarehouses; +using Strata.ContinuousImprovement.Biz.GMDN; +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.OpportunityComments; +using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks; +using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks; +using Strata.ContinuousImprovement.Biz.Population; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.DbContexts; + +public class CentralDbContext : DbContext, ICentralDbContext +{ + public static string MigrationsAssembly = typeof(CentralDbContext).Assembly.GetName().Name; + + // Application Entities + public virtual DbSet NetworkOpportunities { get; set; } + public virtual DbSet ClientNetworkOpportunities { get; set; } + public virtual DbSet ClientNetworkOpportunityLinks { get; set; } + public virtual DbSet Comments { get; set; } + public virtual DbSet OpportunityWorkbooks { get; set; } + public virtual DbSet OpportunityTypeWorkbooks { get; set; } + public virtual DbSet OpportunityRoleAssignments { get; set; } + public virtual DbSet ClientWarehouses { get; set; } + public virtual DbSet ExplorationPopulations { get; set; } + public virtual DbSet ExplorationInclusionExclusions { get; set; } + public virtual DbSet ExplorationPopulationEncounters { get; set; } + public virtual DbSet StrategicOpportunities { get; set; } + public virtual DbSet StrategicOpportunityChargeCodes { get; set; } + public virtual DbSet StrategicOpportunityGLs { get; set; } + public virtual DbSet StrategicOpportunityMeasures { get; set; } + public virtual DbSet StrategicOpportunityRampUps { get; set; } + public virtual DbSet StrategicOpportunityWorkbooks { get; set; } + public virtual DbSet StakeholdersLinks { get; set; } + + // GMDN + public virtual DbSet GMDNTerms { get; set; } + public virtual DbSet GMDNCategories { get; set; } + public virtual DbSet GMDNTermCategories { get; set; } + + // GUDID + public virtual DbSet GUDIDTerms { get; set; } + public virtual DbSet GUDIDDevices { get; set; } + public virtual DbSet GUDIDIdentifiers { get; set; } + public virtual DbSet TmpGUDIDIdentifiers { get; set; } + + // GMDN + GUDID Dimension + public virtual DbSet DeviceIdentifierDimension { get; set; } + public virtual DbSet DeviceIdentifierTerms { get; set; } // view that supports seeding dimension + + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(o => new { o.StrataId, o.NetworkOpportunityId }); + modelBuilder.Entity().HasKey(x => new { x.TermCode, x.CategoryCode }); + modelBuilder.Entity() + .ToView("device_identifier_terms") + .HasKey(di => new { di.DeviceId, di.PrimaryDI, di.DeviceIdType }); + modelBuilder.Entity() + .HasKey(di => new { di.DeviceIdentifier }); + modelBuilder.Entity().HasKey(ow => new { ow.StrataId, ow.OpportunityGuid, ow.WorkbookId }); + modelBuilder.Entity().HasKey(c => new { c.OpportunityId, c.IdentityGuid }); + modelBuilder.Entity().HasKey(ow => new { ow.StrataId, ow.OpportunityType, ow.WorkbookId }); + modelBuilder.Entity() + .HasIndex(p => p.PublicVersionDate); + modelBuilder.Entity() + .HasIndex(e => e.DatabaseGuid) + .IsUnique(); + modelBuilder.Entity() + .HasKey(e => e.ExplorationPopulationId); + modelBuilder.Entity() + .HasKey(e => new { e.ExplorationPopulationId, e.EncounterId }); + modelBuilder.Entity() + .HasKey(e => new { e.ExplorationPopulationId, e.EncounterId }); + modelBuilder.Entity() + .HasMany(g => g.ExplorationPopulationEncounters) + .WithOne(m => m.ExplorationPopulation) + .HasForeignKey(m => m.ExplorationPopulationId) + .HasPrincipalKey(g => g.ExplorationPopulationId); + modelBuilder.Entity() + .HasMany(g => g.ExplorationInclusionExclusions) + .WithOne(m => m.ExplorationPopulation) + .HasForeignKey(m => m.ExplorationPopulationId) + .HasPrincipalKey(g => g.ExplorationPopulationId); + modelBuilder.Entity().UseTptMappingStrategy(); + modelBuilder.Entity() + .HasMany(e => e.Measures) + .WithOne(e => e.Opportunity) + .HasForeignKey(e => e.OpportunityId) + .HasPrincipalKey(e => e.OpportunityId); + modelBuilder.Entity() + .HasMany(e => e.Workbooks) + .WithOne(e => e.Opportunity) + .HasForeignKey(e => e.OpportunityId) + .HasPrincipalKey(e => e.OpportunityId); + modelBuilder.Entity() + .HasMany(e => e.RampUps) + .WithOne(e => e.Opportunity) + .HasForeignKey(e => e.OpportunityId) + .HasPrincipalKey(e => e.OpportunityId); + modelBuilder.Entity() + .HasKey(e => new + { + e.OpportunityId, + e.WorkbookId + }); + modelBuilder.Entity() + .HasKey(e => new + { + e.OpportunityId, + e.MonthNumber + }); + modelBuilder.Entity() + .HasKey(e => new + { + e.StrategicOpportunityId, + e.UserGuid + }); + modelBuilder.Entity() + .HasMany(e => e.Stakeholders) + .WithOne(e => e.Opportunity) + .HasForeignKey(e => e.StrategicOpportunityId) + .HasPrincipalKey(e => e.OpportunityId); + } + + public CentralDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + // Intenionally left empty + } + + + /// + /// 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 + /// + public async Task BeginTextImportAsync(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(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 WITH (DELIMITER '{delimiter}') "; + + return await npgsqlConnection.BeginTextImportAsync(beginTextImportQuery, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Dapper/TypeHandlers.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Dapper/TypeHandlers.cs new file mode 100644 index 0000000..1576213 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Dapper/TypeHandlers.cs @@ -0,0 +1,24 @@ +using Dapper; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Dapper +{ + public class DateOnlyTypeHandler : SqlMapper.TypeHandler + { + public override void SetValue(IDbDataParameter parameter, DateOnly date) + { + parameter.Value = date.ToDateTime(new TimeOnly(0, 0)); + parameter.DbType = DbType.Date; + } + + public override DateOnly Parse(object value) + { + return DateOnly.FromDateTime((DateTime)value); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/DataTableMapper.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/DataTableMapper.cs new file mode 100644 index 0000000..dc85f8b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/DataTableMapper.cs @@ -0,0 +1,14 @@ +using Riok.Mapperly.Abstractions; +using Strata.DataSchema.Models.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class DataTableMapper + { + [MapperIgnoreSource(nameof(DataTable.StrataId))] + [MapperIgnoreSource(nameof(DataColumn.StrataId))] + [MapperIgnoreTarget(nameof(DataTableSaveInfo.UpdateRolePlayingDimTableIds))] + public static partial DataTableSaveInfo MapToSaveInfo(this DataTable src); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Encounter/ENOpportunityDetailQuery.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Encounter/ENOpportunityDetailQuery.cs new file mode 100644 index 0000000..df20207 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Encounter/ENOpportunityDetailQuery.cs @@ -0,0 +1,359 @@ +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.Encounter; +using Strata.ContinuousImprovement.Biz.Encounter.Initiatives; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Schema.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.DbContexts +{ + public partial class JazzDbContext + { + // this code was copied from Jazz + public async Task> GetENOpportunityDetailQuery(ENOpportunityDataModel opportunity, + Configuration.Configuration configuration, ISchemaServiceClient schemaServiceClient, ILogger _logger, Guid dbGuid) + { + var opportunityFilters = JsonConvert.DeserializeObject(opportunity.EncounterOpportunityJSON, + new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }); + var exists = string.Empty; + var possibleCTCategories = new List() { 0, 1, 3 }; + if (opportunityFilters.EncounterGroup == 1 || opportunityFilters.PatientPopulations.Select(pp => Int32.Parse(pp.Id)).Where(pp => pp != 0).Any()) + { + var dimension = await schemaServiceClient.GetDimensionByGlobalIdAsync("Clinical Indicator"); + var hierarchyPaths = opportunityFilters.PatientPopulations.Select(ct => ct.Id); + var patientPopulations = (await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, hierarchyPaths)).Select(mem => Int32.Parse(mem.Id)).ToList(); + exists = $@"AND EXISTS(SELECT 1 FROM dss.FactPatientEncounterClinicalIndicator ci +WHERE ci.EncounterID = fpes.EncounterID AND ci.ClinicalIndicatorID in ({patientPopulations.ToCSVList()}) and ci.IsDeleted = 0)"; + } + else + { + var nonPatientPopDimension = Guid.Parse("7799FFBA-B233-4E72-A629-D0BBA4082F34"); + var hierarchyPaths = opportunityFilters.CaseTypes.Select(ct => ct.Id); + var caseTypeIds = (await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(nonPatientPopDimension, hierarchyPaths)).Select(mem => Int32.Parse(mem.Id)).ToList(); + var cts = (await DimCaseTypes.Where(ct => caseTypeIds.Contains(ct.CaseTypeId) && possibleCTCategories.Contains(ct.CaseTypeCategory)).ToListAsync()) + .Select(ct => new { ct.CaseTypeCategory, ct.CaseTypeId }).ToList() + .GroupBy(ct => ct.CaseTypeCategory, + (g, d) => new + { + CaseTypeCategory = g, + CaseTypeIds = d.Select(x => x.CaseTypeId).ToList() + }).ToList() + .ConvertAll(ct => string.Format(CaseTypeFamilyIdExists.Exists[ct.CaseTypeCategory], ct.CaseTypeIds.ToCSVList())) + .Aggregate("", (x, y) => $"{x}\r\nOR\r\n{y}"); + exists = string.IsNullOrEmpty(cts) || cts.Length <= 6 + ? cts + : $"AND ({cts.Substring(6)}\r\n)"; + } + + var startDate = opportunityFilters.IsBaseline + ? configuration.GetBaselineStartDate(OpportunityTypes.Encounter) + : configuration.GetTrackingStartDate(OpportunityTypes.Encounter); + var endDate = opportunityFilters.IsBaseline + ? configuration.GetBaselineEndDate(OpportunityTypes.Encounter).AddDays(1).Date + : configuration.GetTrackingEndDate().AddDays(1).Date; + var sqlTemplate = await GetSqlTemplate(opportunityFilters, configuration, schemaServiceClient, dbGuid, startDate, endDate, exists); + return await OpportunityMetricCohortData.FromSqlRaw(sqlTemplate, new SqlParameter("@IsBaseline", System.Data.SqlDbType.Bit) { Value = true }).ToListAsync(); + } + + private async Task GetSqlTemplate(ENOpportunityBaseFilters opportunityLevel, Configuration.Configuration configuration, ISchemaServiceClient schemaServiceClient, Guid dbGuid, + DateTime startDate, DateTime endDate, string exists) + { + var queryDetails = new OpportunityQueryDetails() { Exists = exists }; + queryDetails.RevenueColumn = await RevenueColumn(); + queryDetails.CostColumn = await CostColumn(configuration); + queryDetails.BillingJoin = await GetBillingJoin(); + switch (opportunityLevel.EncounterOpportunityLevel) + { + case OpportunityLevel.Organization: + return GetOrganizationSqlTemplate(queryDetails, startDate, endDate); + case OpportunityLevel.Entity: + var entityDimension = await schemaServiceClient.GetDimensionByGlobalIdAsync(configuration.IsUsingCustomEntityDimension ? configuration.DepartmentRollupGlobalId : "Entity"); + var entities = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(entityDimension.DimensionGuid, + opportunityLevel.Entities.Select(sl => sl.Id)); + return GetSystemSqlTemplate(queryDetails, entities.Select(el => Int32.Parse(el.Id)), configuration, startDate, endDate); + case OpportunityLevel.Specialty: + var specialtyDimension = await schemaServiceClient.GetDimensionByGlobalIdAsync("DSS Physician Specialty"); + var specialties = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(specialtyDimension.DimensionGuid, + opportunityLevel.Specialties.Select(sl => sl.Id)); + return GetSpecialtySqlTemplate(queryDetails, specialties.Select(el => Int32.Parse(el.Id)), startDate, endDate); + case OpportunityLevel.ServiceLine: + var serviceLineDimension = await schemaServiceClient.GetClientDimensionAsync(dbGuid, configuration.ServiceLineDimensionGuid); + var serviceLine = serviceLineDimension.Attributes.Find(attr => attr.AttributeGuid == serviceLineDimension.DefaultKeyGuid)?.SqlColumnName; + serviceLine = string.IsNullOrEmpty(serviceLine) ? "ServiceLineID" : serviceLine; + + if (configuration.ServiceLineDimensionGuid != Guid.Empty && + serviceLineDimension.FriendlyName == "Sg2 Service Line Crosswalk") + { + var hierarchyElements = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(configuration.ServiceLineDimensionGuid, + opportunityLevel.ServiceLines.Select(sl => sl.Id)); + return GetSg2ServiceLineSqlTemplate(queryDetails, hierarchyElements.Select(el => Int32.Parse(el.Id)), serviceLine, startDate, endDate); + } + else + { + if (configuration.ServiceLineDimensionGuid != Guid.Empty) + { + var hierarchyElements = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(configuration.ServiceLineDimensionGuid, + opportunityLevel.ServiceLines.Select(sl => sl.Id)); + return GetServiceLineSqlTemplate(queryDetails, hierarchyElements.Select(el => Int32.Parse(el.Id)), serviceLine, startDate, endDate); + } + else + { + var dimension = await schemaServiceClient.GetDimensionByGlobalIdAsync("Service Line"); + var hierarchyElements = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, + opportunityLevel.ServiceLines.Select(sl => sl.Id)); + return GetServiceLineSqlTemplate(queryDetails, hierarchyElements.Select(el => Int32.Parse(el.Id)), serviceLine, startDate, endDate); + } + } + case OpportunityLevel.Provider: + var providerDimension = await schemaServiceClient.GetDimensionByGlobalIdAsync("Physician Primary"); + var providers = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(providerDimension.DimensionGuid, + opportunityLevel.Providers.Select(sl => sl.Id)); + return GetProviderSqlTemplate(queryDetails, providers.Select(el => Int32.Parse(el.Id)), startDate, endDate); + default: + return default; + } + } + + private static string GetOrganizationSqlTemplate(OpportunityQueryDetails queryDetails, DateTime startDate, DateTime endDate) + { + return $@" +;WITH RevenueTotals AS ( +SELECT 0 LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], CAST(SUM({queryDetails.RevenueColumn}) AS DECIMAL) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} +GROUP BY DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT 0 LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +CAST(SUM({queryDetails.CostColumn}) AS DECIMAL) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{queryDetails.BillingJoin} +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} +GROUP BY DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + private static string GetSystemSqlTemplate(OpportunityQueryDetails queryDetails, IEnumerable levelIds, Configuration.Configuration configuration + , DateTime startDate, DateTime endDate) + { + string rollupJoin, rollupColumn; + if (configuration.IsUsingCustomEntityDimension) + { + rollupJoin = configuration.DepartmentRollupGlobalId == "Location" + ? string.Empty + : "INNER JOIN [fw].[DimDepartment] de ON de.DepartmentID = fpes.HighestVariableCostDepartmentID"; + rollupColumn = configuration.DepartmentRollupGlobalId == "Location" + ? "fpes.LocationID" + : $"de.{configuration.DepartmentRollupGlobalId.Replace(" ", string.Empty)}ID"; + } + else + { + rollupJoin = "INNER JOIN clientdss.DimEntity de ON de.EntityID = fpes.EntityID"; + rollupColumn = "de.FwEntityID"; + } + return $@" +;WITH RevenueTotals AS ( +SELECT CAST({rollupColumn} AS INT) LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], CAST(SUM({queryDetails.RevenueColumn}) AS DECIMAL) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +{rollupJoin} +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} +AND {rollupColumn} IN ({levelIds.ToCSVList()}) +GROUP BY {rollupColumn}, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT CAST({rollupColumn} AS INT) LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +CAST(SUM({queryDetails.CostColumn}) AS DECIMAL) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{queryDetails.BillingJoin} +{rollupJoin} +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} + AND {rollupColumn} IN ({levelIds.ToCSVList()}) +GROUP BY {rollupColumn}, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + private static string GetSpecialtySqlTemplate(OpportunityQueryDetails queryDetails, IEnumerable levelIds, DateTime startDate, DateTime endDate) + { + return $@" +;WITH RevenueTotals AS ( +SELECT ps.PhysicianSpecialtyID LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], CAST(SUM({queryDetails.RevenueColumn}) AS DECIMAL) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +INNER JOIN dss.viewDimPrimaryPhysician pp ON pp.PrimaryPhysicianID = fpes.PrimaryPhysicianID +INNER JOIN dss.DimPhysicianSpecialty ps ON ps.PhysicianSpecialtyID = pp.PhysicianSpecialtyID +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} +AND ps.PhysicianSpecialtyID IN ({levelIds.ToCSVList()}) +GROUP BY ps.PhysicianSpecialtyID, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT ps.PhysicianSpecialtyID LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +CAST(SUM({queryDetails.CostColumn}) AS DECIMAL) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{queryDetails.BillingJoin} +INNER JOIN dss.viewDimPrimaryPhysician pp ON pp.PrimaryPhysicianID = fpes.PrimaryPhysicianID +INNER JOIN dss.DimPhysicianSpecialty ps ON ps.PhysicianSpecialtyID = pp.PhysicianSpecialtyID +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} + AND ps.PhysicianSpecialtyID IN ({levelIds.ToCSVList()}) +GROUP BY ps.PhysicianSpecialtyID, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + private static string GetSg2ServiceLineSqlTemplate(OpportunityQueryDetails queryDetails, IEnumerable levelIds, string serviceLine, DateTime startDate, DateTime endDate) + { + return $@" +;WITH RevenueTotals AS ( +SELECT sl.{serviceLine} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], CAST(SUM({queryDetails.RevenueColumn}) AS DECIMAL) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +INNER JOIN dss.DimSg2CareFamily cf ON cf.Sg2CAREFamilyID = fpes.Sg2CAREFamilyID +INNER JOIN dss.DimSg2ServiceLineCrosswalk sl ON sl.Sg2ServiceLine = cf.ServiceLine +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} +AND sl.{serviceLine} IN ({levelIds.ToCSVList()}) +GROUP BY sl.{serviceLine}, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT sl.{serviceLine} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +CAST(SUM({queryDetails.CostColumn}) AS DECIMAL) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{queryDetails.BillingJoin} +INNER JOIN dss.DimSg2CareFamily cf ON cf.Sg2CAREFamilyID = fpes.Sg2CAREFamilyID +INNER JOIN dss.DimSg2ServiceLineCrosswalk sl ON sl.Sg2ServiceLine = cf.ServiceLine +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} + AND sl.{serviceLine} IN ({levelIds.ToCSVList()}) +GROUP BY sl.{serviceLine}, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + private static string GetServiceLineSqlTemplate(OpportunityQueryDetails queryDetails, IEnumerable levelIds, string serviceLine, DateTime startDate, DateTime endDate) + { + return $@" +;WITH RevenueTotals AS ( +SELECT fpes.{serviceLine} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], CAST(SUM({queryDetails.RevenueColumn}) AS DECIMAL) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} +AND fpes.{serviceLine} IN ({levelIds.ToCSVList()}) +GROUP BY fpes.{serviceLine}, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT fpes.{serviceLine} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], +COUNT(DISTINCT(fpes.EncounterID)) Volume, +CAST(SUM({queryDetails.CostColumn}) AS DECIMAL) Cost, +@IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{queryDetails.BillingJoin} +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} + AND fpes.{serviceLine} IN ({levelIds.ToCSVList()}) +GROUP BY fpes.{serviceLine}, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + private static string GetProviderSqlTemplate(OpportunityQueryDetails queryDetails, IEnumerable levelIds, DateTime startDate, DateTime endDate) + { + return $@" +;WITH RevenueTotals AS ( +SELECT dp.PhysicianID LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], CAST(SUM({queryDetails.RevenueColumn}) AS DECIMAL) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +INNER JOIN dss.DimPhysician dp on dp.PhysicianID = fpes.PrimaryPhysicianID +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} +AND dp.PhysicianID IN ({levelIds.ToCSVList()}) +GROUP BY dp.PhysicianID, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT dp.PhysicianID LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +CAST(SUM({queryDetails.CostColumn}) AS DECIMAL) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{queryDetails.BillingJoin} +INNER JOIN dss.DimPhysician dp on dp.PhysicianID = fpes.PrimaryPhysicianID +WHERE fpes.DischargeDateTime >= '{startDate:d}' AND fpes.DischargeDateTime < '{endDate:d}' +{queryDetails.Exists} + AND dp.PhysicianID IN ({levelIds.ToCSVList()}) +GROUP BY dp.PhysicianID, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + private async Task RevenueColumn() + { + var settingValue = Convert.ToBoolean(Convert.ToInt32((await SystemSettings.SingleOrDefaultAsync(ss => ss.Name == "Encounter Initiatives Revenue CSF")).Value)); + return settingValue ? "fpes.CustomEncounterInitiativesRevenue" : "fpes.NetRevenue"; + } + + private async Task CostColumn(Configuration.Configuration config) + { + var settingValue = Convert.ToInt32((await SystemSettings.SingleOrDefaultAsync(ss => ss.Name == "Encounter Initiatives Cost Type")).Value); + switch ((EncounterInitiativeCostType)settingValue) + { + case EncounterInitiativeCostType.EncounterSummary: + return config.IsUsingIndirectCost ? "fpes.VariableIndirectCost + fpes.VariableDirectCost" : "fpes.VariableDirectCost"; + case EncounterInitiativeCostType.CSF: + return "fpes.CustomEncounterInitiativesCost"; + case EncounterInitiativeCostType.DetailBilling: + return config.IsUsingIndirectCost ? "billing.VariableIndirectCost + billing.VariableDirectCost" : "billing.VariableDirectCost"; + } + + return ""; + } + + private async Task GetBillingJoin() + { + var settingValue = Convert.ToInt32((await SystemSettings.SingleOrDefaultAsync(ss => ss.Name == "Encounter Initiatives Cost Type")).Value); + if ((EncounterInitiativeCostType)settingValue is EncounterInitiativeCostType.DetailBilling) + { + return "INNER JOIN dss.viewFactPatientBillingLineItemDetailWithCosts billing ON billing.EncounterId = fpes.EncounterId "; + } + return ""; + } + } + + internal class OpportunityQueryDetails + { + public string Exists { get; set; } + public string RevenueColumn { get; set; } + public string CostColumn { get; set; } + public string BillingJoin { get; set; } + } + + internal static class CaseTypeFamilyIdExists + { + public static string[] Exists => new[] + { + @" EXISTS(SELECT 1 FROM dss.CaseTypeFamilyMappingICD10 msdrg + INNER JOIN dss.DimCaseType dct ON dct.CaseTypeFamilyID = msdrg.CaseTypeFamilyID AND dct.CaseTypeId in ({0}) AND dct.CaseTypeCategory = 0 + WHERE msdrg.CaseTypeFamilyID = fpes.MSDRGCaseTypeFamilyID)", + @" EXISTS(SELECT 1 FROM dss.CaseTypeFamilyMappingAPRDRG aprdrg + INNER JOIN dss.DimCaseType dct ON dct.CaseTypeFamilyID = aprdrg.CaseTypeFamilyID AND dct.CaseTypeId in ({0}) AND dct.CaseTypeCategory = 1 + WHERE aprdrg.CaseTypeFamilyID = fpes.APRDRGCaseTypeFamilyID)", + @"AND EXISTS(SELECT 1 FROM dss.FactPatientEncounterClinicalIndicator ci +WHERE ci.EncounterID = fpes.EncounterID AND ci.ClinicalIndicatorID in ({0}) and ci.IsDeleted = 0)", + @" EXISTS(SELECT 1 FROM dss.CaseTypeFamilyMappingCPT cpt + INNER JOIN dss.DimCaseType dct ON dct.CaseTypeFamilyID = cpt.CaseTypeFamilyID AND dct.CaseTypeId in ({0}) AND dct.CaseTypeCategory = 3 + WHERE cpt.CaseTypeFamilyID = fpes.CPTCodeCaseTypeFamilyID)" + }; + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/ICentralDbContext.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/ICentralDbContext.cs new file mode 100644 index 0000000..a116c5d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/ICentralDbContext.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.GMDN; +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.OpportunityComments; +using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks; +using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; + +namespace Strata.ContinuousImprovement.Biz.DbContexts +{ + public interface ICentralDbContext + { + DbSet ClientNetworkOpportunities { get; set; } + DbSet ClientNetworkOpportunityLinks { get; set; } + DbSet Comments { get; set; } + DbSet DeviceIdentifierDimension { get; set; } + DbSet DeviceIdentifierTerms { get; set; } + DbSet GMDNCategories { get; set; } + DbSet GMDNTermCategories { get; set; } + DbSet GMDNTerms { get; set; } + DbSet GUDIDDevices { get; set; } + DbSet GUDIDIdentifiers { get; set; } + DbSet GUDIDTerms { get; set; } + DbSet NetworkOpportunities { get; set; } + DbSet OpportunityTypeWorkbooks { get; set; } + DbSet OpportunityRoleAssignments { get; set; } + DbSet OpportunityWorkbooks { get; set; } + DbSet TmpGUDIDIdentifiers { get; set; } + DbSet StrategicOpportunities { get; set; } + DbSet StrategicOpportunityChargeCodes { get; set; } + DbSet StrategicOpportunityGLs { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/IJazzDbContext.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/IJazzDbContext.cs new file mode 100644 index 0000000..c0df46e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/IJazzDbContext.cs @@ -0,0 +1,53 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Encounter; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using Strata.ContinuousImprovement.Biz.Security; +using Strata.ContinuousImprovement.Biz.UserProfiles; +using Strata.ContinuousImprovement.Biz.UserSetting; +using Strata.ContinuousImprovement.Biz.UtilizationVariation; +using Strata.Schema.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.DbContexts +{ + public interface IJazzDbContext + { + DbSet Configurations { get; set; } + DbSet AllOpportunities { get; set; } + DbSet ChargeCodeOpportunities { get; set; } + DbSet EncounterOpportunities { get; set; } + DbSet FFOpportunities { get; set; } + DbSet GeneralLedgerOpportunities { get; set; } + DbSet PayrollOpportunities { get; set; } + DbSet VariationOpportunities { get; set; } + DbSet VariationCaseTypes { get; set; } + DbSet DimCaseTypes { get; set; } + DbSet DimEntities { get; set; } + DbSet QualityVariationItemOpportunities { get; set; } + DbSet QualityVariationItemEvents { get; set; } + DbSet QviMetrics { get; set; } + DbSet QualityVariationItemBreakdowns { get; set; } + DbSet UserSettings { get; set; } + DbSet FactGLSampledData { get; set; } + DbSet FactGLData { get; set; } + DbSet FactStatisticSampledMonthlyData { get; set; } + DbSet FactStatisticData { get; set; } + DbSet UserProfiles { get; set; } + DbSet DboUserSettings { get; set; } + public DbSet SimplePermissions { get; set; } + public DbSet S3SimplePermissions { get; set; } + DbSet InitiativeRollupColumns { get; set; } + DbSet InitiativeRollupValues { get; set; } + + IQueryable AddUserFilter(IQueryable query, ClaimsPrincipal user); + Task> GetENOpportunityDetailQuery(ENOpportunityDataModel opportunity, + Configuration.Configuration configuration, ISchemaServiceClient schemaServiceClient, ILogger _logger, Guid dbGuid); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/InitiativeWorkstreamsByConfigurationQuery.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/InitiativeWorkstreamsByConfigurationQuery.cs new file mode 100644 index 0000000..a1bd75e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/InitiativeWorkstreamsByConfigurationQuery.cs @@ -0,0 +1,19 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.DbContexts +{ + public partial class JazzDbContext + { + public IQueryable InitiativeWorkstreamsByConfigurationQuery(Guid configurationGuid) + { + var result = Initiatives + .Where(i => i.ConfigurationGUID == configurationGuid) + .Select(i => new WorkstreamDto { Workstream = i.Workstream }).Distinct().OrderBy(i => i.Workstream); + return result; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/InitiativeWorkstreamsQuery.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/InitiativeWorkstreamsQuery.cs new file mode 100644 index 0000000..3a50158 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/InitiativeWorkstreamsQuery.cs @@ -0,0 +1,16 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.DbContexts +{ + public partial class JazzDbContext + { + public IQueryable InitiativeWorkstreamsQuery() + { + var result = Initiatives.Select(i => new WorkstreamDto { Workstream = i.Workstream }).Distinct().OrderBy(i => i.Workstream); + return result; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimAprdrg.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimAprdrg.cs new file mode 100644 index 0000000..c12b2d2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimAprdrg.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Jazz.Dimensions +{ + [Table("DimAprdrg", Schema = "dss")] + public class DimAprdrg + { + [Key] + public int AprDrgId { get; set; } + public string AprDrgCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimCpt.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimCpt.cs new file mode 100644 index 0000000..1bfdf3b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimCpt.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Jazz.Dimensions +{ + [Table("DimCpt", Schema = "dss")] + public class DimCpt + { + [Key] + public int CptId { get; set; } + public string CptCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimIcd10Dx.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimIcd10Dx.cs new file mode 100644 index 0000000..1ebb6c2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimIcd10Dx.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Jazz.Dimensions +{ + [Table("DimICD10DX", Schema = "dss")] + public class DimIcd10Dx + { + [Key] + public int Icd10DxId { get; set; } + public string Icd10DxCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimIcd10Px.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimIcd10Px.cs new file mode 100644 index 0000000..367e6ce --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimIcd10Px.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Jazz.Dimensions +{ + [Table("DimICD10Px", Schema = "dss")] + public class DimIcd10Px + { + [Key] + public int Icd10PxId { get; set; } + public string Icd10PxCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimMsdrg.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimMsdrg.cs new file mode 100644 index 0000000..75fae4c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimMsdrg.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Jazz.Dimensions +{ + [Table("DimMsdrg", Schema = "dss")] + public class DimMsdrg + { + [Key] + public int MsdrgId { get; set; } + public string MsdrgCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimMsdrgRollup.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimMsdrgRollup.cs new file mode 100644 index 0000000..7198497 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Jazz/Dimensions/DimMsdrgRollup.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Jazz.Dimensions +{ + [Table("DimMsdrgRollup", Schema = "dss")] + public class DimMsdrgRollup + { + [Key] + public int MsdrgRollupId { get; set; } + public string MsdrgRollupCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/JazzDbContext.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/JazzDbContext.cs new file mode 100644 index 0000000..36ca597 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/JazzDbContext.cs @@ -0,0 +1,304 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Cube; +using Strata.ContinuousImprovement.Biz.DbContexts.Jazz.Dimensions; +using Strata.ContinuousImprovement.Biz.DbContexts.Reports; +using Strata.ContinuousImprovement.Biz.DbContexts.Security; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies; +using Strata.ContinuousImprovement.Biz.Exploration.Opportunities; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives; +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Details; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.GMDN; +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities; +using Strata.ContinuousImprovement.Biz.PeerGroups; +using Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns; +using Strata.ContinuousImprovement.Biz.Score; +using Strata.ContinuousImprovement.Biz.Security; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.UserProfiles; +using Strata.ContinuousImprovement.Biz.UserSetting; +using Strata.ContinuousImprovement.Biz.UtilizationVariation; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes; +using Strata.CoreLib.Claims.Extensions; +using Strata.Stratasphere.Biz.DbContexts; +using System; +using System.Linq; +using System.Security.Claims; + +namespace Strata.ContinuousImprovement.Biz.DbContexts +{ + + public partial class JazzDbContext : DbContext, IJazzDbContext + { + public DbSet Configurations { get; set; } + public DbSet SystemSettings { get; set; } + public DbSet ScoreDimensions { get; set; } + public DbSet ViewScoreAttributes { get; set; } + public DbSet ScoreAttributes { get; set; } + public DbSet AllOpportunities { get; set; } + public DbSet ExplorationOpportunities { get; set; } + public DbSet EncounterOpportunities { get; set; } + public DbSet EncounterOpportunityMetricData { get; set; } + public DbSet OpportunityMetricCohortData { get; set; } + public DbSet ENOpportunities { get; set; } + public DbSet ExplorationVariationOpportunitiesData { get; set; } + public DbSet ChargeCodes { get; set; } + public DbSet ChargeCodeOpps { get; set; } + public DbSet ChargeCodeOpportunities { get; set; } + public DbSet ChargeCodeDetails { get; set; } + public DbSet ChargeCodeOpportunityLevelCases { get; set; } + public DbSet ChargeCodeDetailsQueryResults { get; set; } + public DbSet ChargeCodeOpportunityLevelCaseQueryResults { get; set; } + public DbSet FFOpportunities { get; set; } + public DbSet FreeFormOpportunities { get; set; } + public DbSet FreeFormInitiativeTrackingData { get; set; } + public DbSet FinancialFreeFormInitiativeTrackingData { get; set; } + public DbSet GeneralLedgerOpportunities { get; set; } + public DbSet GeneralLedgerOpportunitiesDataModel { get; set; } + public DbSet PayrollOpportunities { get; set; } + public DbSet PayrollOpportunitiesDataModel { get; set; } + public DbSet VariationOpportunities { get; set; } + public DbSet UtilizationVariationOpportunitiesData { get; set; } + public DbSet NetworkOpportunityLinks { get; set; } + public DbSet VariationCaseTypes { get; set; } + public DbSet DimCaseTypes { get; set; } + public DbSet DimEntities { get; set; } + public DbSet QualityVariationItemOpportunities { get; set; } + public DbSet QualityVariationItemBreakdowns { get; set; } + public DbSet QualityVariationItemEvents { get; set; } + public DbSet QviMetrics { get; set; } + public DbSet UserSettings { get; set; } + public DbSet FactGLSampledData { get; set; } + public DbSet FactGLData { get; set; } + public DbSet FactStatisticSampledMonthlyData { get; set; } + public DbSet FactStatisticData { get; set; } + public DbSet Initiatives { get; set; } + public DbSet InitiativeComments { get; set; } + public DbSet InitiativeCriteria { get; set; } + public DbSet InitiativeScore { get; set; } + public DbSet InitiativeCriteriaQueryResults { get; set; } + public DbSet DimSupply { get; set; } + public DbSet DimDeviceIdentifiers { get; set; } + + public DbSet OpportunityReports { get; set; } + public DbSet SourceOfSavingsReports { get; set; } + public DbSet REReports { get; set; } + public DbSet Folders { get; set; } + public DbSet S3ACLSimples { get; set; } + public DbSet SimplePermissions { get; set; } + public DbSet S3SimplePermissions { get; set; } + public DbSet S3SimplePermissionDefaults { get; set; } + public DbSet Identities { get; set; } + public DbSet UserGroups { get; set; } + public DbSet UserProfiles { get; set; } + public DbSet DboUserSettings { get; set; } + public DbSet InitiativeRollupColumns { get; set; } + public DbSet PeerGroups { get; set; } + public DbSet PeerGroupCriteriaMappings { get; set; } + public DbSet InitiativeRollupValues { get; set; } + public DbSet DimCostComponents { get; set; } + public DbSet CostComponentRollups { get; set; } + public DbSet CaseTypeFamilies { get; set; } + public DbSet Locks { get; set; } + + public DbSet DimAprDrgs { get; set; } + public DbSet DimMsdrgs { get; set; } + public DbSet DimMsdrgRollups { get; set; } + public DbSet DimIcd10Dxs { get; set; } + public DbSet DimIcd10Pxs { get; set; } + public DbSet DimCpts { get; set; } + +#pragma warning disable S100 // Methods and properties should be named in PascalCase + public DbSet RESQLReportRegisteredAttributes { get; set; } +#pragma warning restore S100 // Methods and properties should be named in PascalCase + + private ILogger _logger { get; set; } + + public IDatabaseFunctionService DatabaseFunctionService { get; set; } + + public JazzDbContext(DbContextOptions options) : base(options) + { + var loggerFactory = (ILoggerFactory)new LoggerFactory(); + _logger = loggerFactory.CreateLogger(); + DatabaseFunctionService = new DatabaseFunctionService(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .Property(p => p.OpportunityId) + .Metadata.SetAfterSaveBehavior(Microsoft.EntityFrameworkCore.Metadata.PropertySaveBehavior.Ignore); + + modelBuilder.Entity() + .Property(p => p.OpportunityId) + .Metadata.SetAfterSaveBehavior(Microsoft.EntityFrameworkCore.Metadata.PropertySaveBehavior.Ignore); + + modelBuilder.Entity() + .HasKey(cc => new + { + cc.ChargeCodeId + }); + + modelBuilder.Entity() + .ToTable(tb => tb.UseSqlOutputClause(false)) + .HasAlternateKey(i => new + { + i.ConfigurationGUID, + i.InitiativeGUID + }); + modelBuilder.Entity() + .HasKey(vct => new + { + vct.ConfigurationGuid, + vct.CaseTypeId, + vct.ServiceLineId + }); + modelBuilder.Entity() + .HasKey(vct => new + { + vct.ConfigurationGuid, + vct.QualityVariationEventId + }); + modelBuilder.Entity() + .HasKey(b => new + { + b.ConfigurationGuid, + b.BreakdownId + }); + modelBuilder.Entity() + .HasKey(gl => new + { + gl.DepartmentID, + gl.AccountID, + gl.FiscalYearID, + gl.FiscalMonthID + }); + modelBuilder.Entity() + .HasKey(gl => new + { + gl.DepartmentID, + gl.AccountID, + gl.FiscalYearID, + gl.FiscalMonthID + }); + modelBuilder.Entity() + .HasKey(gl => new + { + gl.DepartmentID, + gl.AccountID, + gl.FiscalYearID, + gl.FiscalMonthID + }); + modelBuilder.Entity() + .HasKey(gl => new + { + gl.DepartmentID, + gl.AccountID, + gl.FiscalYearID, + gl.FiscalMonthID + }); + + modelBuilder.Entity() + .HasNoKey(); + modelBuilder.Entity() + .HasNoKey(); + + modelBuilder.Entity() + .HasKey(lc => new + { + lc.OpportunityGuid, + lc.Month, + lc.OpportunityLevelId, + lc.IsBaseline + }); + + modelBuilder.Entity() + .HasNoKey(); + modelBuilder.Entity() + .ToTable(tb => tb.UseSqlOutputClause(false)); + modelBuilder.Entity() + .ToTable(tb => tb.UseSqlOutputClause(false)); + modelBuilder.Entity() + .ToTable(tb => tb.UseSqlOutputClause(false)); + modelBuilder.Entity().HasKey(or => new { or.OpportunityGUID, or.ReportGUID }); + modelBuilder.Entity().HasKey(sr => new { sr.SourceOfSavings, sr.ReportGUID }); + modelBuilder.Entity().HasKey(s => new { s.UserGuid, s.LootId }); + modelBuilder.Entity().HasKey(pd => new { pd.EntityType, pd.IdentityGuid }); + modelBuilder.Entity().HasNoKey(); + modelBuilder.Entity().HasKey(di => new { di.DeviceIdentifier }); + modelBuilder.Entity() + .HasMany(e => e.InitiativeRollupValues) + .WithOne(e => e.InitiativeRollupColumn) + .HasPrincipalKey(e => e.InitiativeRollupColumnId); + modelBuilder.Entity() + .HasMany(e => e.CostComponents) + .WithOne(e => e.CostComponentRollup) + .HasPrincipalKey(e => e.CostComponentRollupGUID); + modelBuilder.Entity() + .ToTable(tb => tb.UseSqlOutputClause(false)); + modelBuilder.Entity() + .HasAlternateKey(nol => new + { + nol.OpportunityId + }); + modelBuilder.Entity() + .HasNoKey(); + modelBuilder.HasDbFunction(() => SqlFunctions.Split(default, default)); + + modelBuilder.Entity() + .HasMany(g => g.Mappings) + .WithOne(m => m.PeerGroup) + .HasForeignKey(m => m.PeerGroupId) + .HasPrincipalKey(g => g.PeerGroupId); + + modelBuilder.Entity() + .HasKey(e => e.DimensionGuid); + } + + public IQueryable AddUserFilter(IQueryable query, ClaimsPrincipal user) + { + if (user == null || user.IsSdtEmployee()) return query; + var userGuid = user.GetUserGuid(); + var obj = Activator.CreateInstance(); + if (obj is IOpportunity) + { + return query + .Where(o => (from s3 in S3SimplePermissions + where s3.IdentityGUID == userGuid && s3.LootID == ((IOpportunity)o).OpportunityGuid && s3.ReadValue + select ((IOpportunity)o).OpportunityGuid).Contains(((IOpportunity)o).OpportunityGuid) + || ((from s3 in S3ACLSimples + where s3.UserGuid == userGuid && s3.LootId == ((IOpportunity)o).OpportunityGuid && s3.IsReadable + select ((IOpportunity)o).OpportunityGuid).Contains(((IOpportunity)o).OpportunityGuid) + && !(from s3 in S3SimplePermissions + where s3.IdentityGUID == userGuid && s3.LootID == ((IOpportunity)o).OpportunityGuid && !s3.ReadValue + select ((IOpportunity)o).OpportunityGuid).Contains(((IOpportunity)o).OpportunityGuid))); + } + else if (obj is IInitiative) + { + return query.Where(i => + (from s3 in S3SimplePermissions + where s3.IdentityGUID == userGuid && s3.LootID == ((IInitiative)i).InitiativeGUID && s3.ReadValue + select ((IInitiative)i).InitiativeGUID).Contains(((IInitiative)i).InitiativeGUID) + || ((from s3 in S3ACLSimples + where s3.UserGuid == userGuid && s3.LootId == ((IInitiative)i).InitiativeGUID && s3.IsReadable + select ((IInitiative)i).InitiativeGUID).Contains(((IInitiative)i).InitiativeGUID) + && !(from s3 in S3SimplePermissions + where s3.IdentityGUID == userGuid && s3.LootID == ((IInitiative)i).InitiativeGUID && !s3.ReadValue + select ((IInitiative)i).InitiativeGUID).Contains(((IInitiative)i).InitiativeGUID))); + } + return query; + } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/RESQLReportRegisteredAttribute.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/RESQLReportRegisteredAttribute.cs new file mode 100644 index 0000000..1974a33 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/RESQLReportRegisteredAttribute.cs @@ -0,0 +1,18 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts +{ + [Table("RESQLReportRegisteredAttribute", Schema = "dbo")] +#pragma warning disable S100 // Methods and properties should be named in PascalCase + public class RESQLReportRegisteredAttribute +#pragma warning restore S100 // Methods and properties should be named in PascalCase + { + [Key] + public Guid RegisteredAttributeGUID { get; set; } + public Guid RegisteredDimensionGUID { get; set; } + public Guid AttributeGUID { get; set; } + public string ColumnAlias { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/Folder.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/Folder.cs new file mode 100644 index 0000000..3b9dd9d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/Folder.cs @@ -0,0 +1,24 @@ +using Strata.Stratasphere.Biz.DbContexts; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Reports +{ + [Table("Folder", Schema = "dbo")] + public class Folder + { + [Key] + public Guid FolderGuid { get; set; } + public Guid ParentFolderGuid { get; set; } + public string Name { get; set; } + //public int DisplayOrder { get; set; } + //public int OutlineLevel { get; set; } + + [NotMapped] + public List Folders { get; set; } + [NotMapped] + public List Reports { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/FolderTree.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/FolderTree.cs new file mode 100644 index 0000000..3e246ae --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/FolderTree.cs @@ -0,0 +1,30 @@ +using Strata.Stratasphere.Biz.DbContexts; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Reports +{ + public class FolderTree + { + public string Key { get; set; } + public string Title { get; set; } + public List Children { get; set; } + + public FolderTree() { } + + public FolderTree(Folder folder) + { + Key = folder.FolderGuid.ToString(); + Title = folder.Name; + if (!folder.Folders.Any() && !folder.Reports.Any()) { return; } + Children = folder.Folders.Select(x => new FolderTree(x)) + .Union(folder.Reports.Select(x => new FolderTree(x))).ToList(); + } + + public FolderTree(REReport report) + { + Key = report.ReportGUID.ToString(); + Title = report.Name; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/OpportunityReport.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/OpportunityReport.cs new file mode 100644 index 0000000..becbe4d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/OpportunityReport.cs @@ -0,0 +1,12 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.Stratasphere.Biz.DbContexts +{ + [Table("OpportunityReports", Schema = "cci")] + public class OpportunityReport + { + public Guid OpportunityGUID { get; set; } + public Guid ReportGUID { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/REReport.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/REReport.cs new file mode 100644 index 0000000..b1c461b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/REReport.cs @@ -0,0 +1,18 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.Stratasphere.Biz.DbContexts +{ + [Table("REReport", Schema = "dbo")] + public class REReport + { + [Key] + public Guid ReportGUID { get; set; } + public string Name { get; set; } + public Guid FolderGuid { get; set; } + + [NotMapped] + public bool IsOpportunityReport { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/SourceOfSavingsReport.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/SourceOfSavingsReport.cs new file mode 100644 index 0000000..de12891 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Reports/SourceOfSavingsReport.cs @@ -0,0 +1,12 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.Stratasphere.Biz.DbContexts +{ + [Table("SourceOfSavingsReports", Schema = "cci")] + public class SourceOfSavingsReport + { + public int SourceOfSavings { get; set; } + public Guid ReportGUID { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Security/S3ACLSimple.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Security/S3ACLSimple.cs new file mode 100644 index 0000000..b306cdf --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Security/S3ACLSimple.cs @@ -0,0 +1,15 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Security +{ + [Table("viewS3ACLSimple", Schema = "dbo")] + public class S3ACLSimple + { + public Guid UserGuid { get; set; } + public Guid LootId { get; set; } + public bool IsReadable { get; set; } + public bool IsWritable { get; set; } + public bool IsSecurable { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/ISnowflakeDatabaseContext.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/ISnowflakeDatabaseContext.cs new file mode 100644 index 0000000..1ad34a5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/ISnowflakeDatabaseContext.cs @@ -0,0 +1,28 @@ +using Dapper; +using Strata.SnowflakeLib; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Snowflake +{ + public interface ISnowflakeDatabaseContext + { + Task GetSnowflakeConnectionStringBuilderAsync(); + + Task> QueryAsync(string query, string requestType, object parameters = null, TimeSpan timeSpan = default); + + Task QueryMultipleAsync(string query, string requestType, object parameters = null, TimeSpan timeSpan = default); + + Task> QueryMultipleReturnLastAsync(string query, string requestType, byte queryCount, object parameters = null, TimeSpan timeSpan = default); + + Task QueryFirstOrDefault(string query, object parameters = null); + + Task ExecuteScalarAsync(string query, object parameters = null); + Task ExecuteCommandAsync(string queryString, object parameters = null, TimeSpan timeSpan = default); + Task ExecuteCommand(string queryString, object parameters = null, TimeSpan timeSpan = default); + Task BulkInsert(string stageName, string tableName, IEnumerable entities, string sqlClearData, IEnumerable columnsToIgnore); + + Task BulkUpdate(string stageName, string tableName, IEnumerable entities, string tempTableName, string tempTableFields, IEnumerable keyColumns, IEnumerable valueColumns); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/ISnowflakeDatabaseContextFactory.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/ISnowflakeDatabaseContextFactory.cs new file mode 100644 index 0000000..d505516 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/ISnowflakeDatabaseContextFactory.cs @@ -0,0 +1,7 @@ +namespace Strata.ContinuousImprovement.Biz.DbContexts.Snowflake +{ + public interface ISnowflakeDatabaseContextFactory + { + public SnowflakeDatabaseContext Create(string databaseGuid); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/ISqlGridReaderWrapper.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/ISqlGridReaderWrapper.cs new file mode 100644 index 0000000..a43dcd7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/ISqlGridReaderWrapper.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Snowflake +{ + public interface ISqlGridReaderWrapper : IDisposable + { + IEnumerable Read(bool buffered = true); + IEnumerable Read(bool buffered = true); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SnowflakeDatabaseContext.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SnowflakeDatabaseContext.cs new file mode 100644 index 0000000..42a019d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SnowflakeDatabaseContext.cs @@ -0,0 +1,286 @@ +using CsvHelper.Configuration; +using CsvHelper; +using Dapper; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Snowflake.Data.Client; +using Strata.ContinuousImprovement.Biz.ClientWarehouses; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.SnowflakeLib; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using System.Data; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Snowflake +{ + public class SnowflakeDatabaseContext : DbContext, ISnowflakeDatabaseContext + { + public const string DEFAULT_CI_WAREHOUSE = "APP_CI_EXPLORATION_XS_WH"; + private string _databaseGuid { get; } + + private readonly SnowflakeClientDbConnectionStringBuilderFactory _snowflakeClientDbConnectionStringBuilderFactory; + private readonly IClientWarehouseService _clientWarehouseService; + + public SnowflakeDatabaseContext(string databaseGuid, + SnowflakeClientDbConnectionStringBuilderFactory snowflakeClientDbConnectionStringBuilderFactory, + IClientWarehouseService clientWarehouseService) + { + _databaseGuid = databaseGuid; + _snowflakeClientDbConnectionStringBuilderFactory = snowflakeClientDbConnectionStringBuilderFactory; + _clientWarehouseService = clientWarehouseService; + } + + public async Task GetSnowflakeConnectionStringBuilderAsync() + { + var connection = await _snowflakeClientDbConnectionStringBuilderFactory.CreateAsync(_databaseGuid); + + if (Guid.TryParse(_databaseGuid, out Guid databaseGuid)) + { + var clientWarehouse = await _clientWarehouseService.GetByDatabaseGuid(databaseGuid, CancellationToken.None); + connection.Warehouse = clientWarehouse == null ? + DEFAULT_CI_WAREHOUSE : $"APP_CI_EXPLORATION_{clientWarehouse.WarehouseSize}_WH"; + } + return connection; + } + + public async Task> QueryAsync(string query, string requestType, object parameters = null, TimeSpan timeSpan = default) + { + SnowflakeConnectionStringBuilder connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync(); + if (!timeSpan.Equals(default(TimeSpan))) connectionBuilder.Timeout = (int)timeSpan.TotalSeconds; + + await using (StrataSnowflakeConnection connection = connectionBuilder.CreateConnection()) + { + connection.AddCustomQueryTags(new Dictionary(new[] { GetRequestTypeQueryTag(requestType) })); + return await connection.QueryAsync(query, parameters); + } + } + + public async Task QueryMultipleAsync(string query, string requestType, object parameters = null, TimeSpan timeSpan = default) + { + SnowflakeConnectionStringBuilder connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync(); + if (!timeSpan.Equals(default(TimeSpan))) connectionBuilder.Timeout = (int)timeSpan.TotalSeconds; + + StrataSnowflakeConnection connection = connectionBuilder.CreateConnection(); + connection.AddCustomQueryTags(new Dictionary(new[] { GetRequestTypeQueryTag(requestType) })); + var sqlReader = await connection.QueryMultipleAsync(query, parameters); + return new SqlGridReaderWrapper(sqlReader); + } + + public async Task> QueryMultipleReturnLastAsync(string query, string requestType, byte queryCount, object parameters = null, TimeSpan timeSpan = default) + { + var result = await QueryMultipleAsync(query, requestType, parameters, timeSpan); + using (result) + { + for (int i = 0; i < queryCount - 1; i++) + { + result.Read(); + } + var finalResult = result.Read().ToList(); + return finalResult; + } + } + + public async Task QueryFirstOrDefault(string query, object parameters = null) + { + SnowflakeConnectionStringBuilder connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync(); + + await using (StrataSnowflakeConnection connection = connectionBuilder.CreateConnection()) + { + return await connection.QueryFirstOrDefaultAsync(query, parameters); + } + + } + + public async Task ExecuteScalarAsync(string query, object parameters = null) + { + SnowflakeConnectionStringBuilder connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync(); + + await using (StrataSnowflakeConnection connection = connectionBuilder.CreateConnection()) + { + return await connection.ExecuteScalarAsync(query, parameters); + } + + } + + public async Task ExecuteCommandAsync(string queryString, object parameters = null, TimeSpan timeSpan = default) + { + var connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync(); + if (!timeSpan.Equals(default(TimeSpan))) connectionBuilder.Timeout = (int)timeSpan.TotalSeconds; + + await using (SnowflakeDbConnection connection = connectionBuilder.CreateConnection()) + { + return await connection.ExecuteAsync(queryString, parameters); + } + } + + // use this method to avoid "Get and Put are not supported in async calls. Use Execute() instead of ExecuteAsync()." error + public async Task ExecuteCommand(string queryString, object parameters = null, TimeSpan timeSpan = default) + { + var connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync(); + if (!timeSpan.Equals(default(TimeSpan))) connectionBuilder.Timeout = (int)timeSpan.TotalSeconds; + + await using (SnowflakeDbConnection connection = connectionBuilder.CreateConnection()) + { + connection.Execute(queryString, parameters); + } + } + + public async Task BulkInsert(string stageName, string tableName, IEnumerable entities, string sqlClearData, IEnumerable columnsToIgnore) + { + var fileName = $"{Guid.NewGuid()}.csv"; + var tempFilePath = Path.Combine(Path.GetTempPath(), fileName); + + try + { + string[] headers; + await using (var writer = new StreamWriter(tempFilePath)) + await using (var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture))) + { + csv.Context.RegisterClassMap(new CsvUtils.DynamicMap(columnsToIgnore)); + await csv.WriteRecordsAsync(entities); + headers = csv.HeaderRecord; + } + + SnowflakeConnectionStringBuilder connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync(); + + await using (StrataSnowflakeConnection connection = connectionBuilder.CreateConnection()) + { + var stageFileName = $"{stageName}/{fileName}"; + using (IDbTransaction transaction = connection.BeginTransaction()) + { + try + { + connection.Execute($"PUT file://{tempFilePath} @{stageFileName} AUTO_COMPRESS=TRUE", transaction: transaction); + if (!string.IsNullOrEmpty(sqlClearData)) + { + await connection.ExecuteAsync($"{sqlClearData}", transaction: transaction); + } + await connection.ExecuteAsync($@" + COPY INTO {tableName} ({string.Join(",", headers)}) + FROM @{stageFileName} + FILE_FORMAT = (TYPE = CSV, SKIP_HEADER = 1); + ", transaction: transaction); + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + finally + { + try + { + await this.ExecuteCommandAsync($"REMOVE @{stageFileName};"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to remove staged file {StageFileName} from Snowflake. Manual cleanup may be required.", stageFileName); + } + } + } + } + } + finally + { + File.Delete(tempFilePath); + } + } + + public async Task BulkUpdate(string stageName, string tableName, IEnumerable entities, string tempTableName, string tempTableFields, IEnumerable keyColumns, IEnumerable valueColumns) + { + var fileName = $"{Guid.NewGuid()}.csv"; + var tempFilePath = Path.Combine(Path.GetTempPath(), fileName); + + try + { + await using (var writer = new StreamWriter(tempFilePath)) + await using (var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture))) + { + await csv.WriteRecordsAsync(entities); + } + + SnowflakeConnectionStringBuilder connectionBuilder = await GetSnowflakeConnectionStringBuilderAsync(); + + await using (StrataSnowflakeConnection connection = connectionBuilder.CreateConnection()) + { + var stageFileName = $"{stageName}/{fileName}"; + using (IDbTransaction transaction = connection.BeginTransaction()) + { + try + { + connection.Execute($"PUT file://{tempFilePath} @{stageFileName} AUTO_COMPRESS=TRUE", transaction: transaction); + await connection.ExecuteAsync($@" + CREATE TEMPORARY TABLE {tempTableName} ( + {tempTableFields} + ); + ", transaction: transaction); + + var sqlCopy = $@" + COPY INTO {tempTableName} + FROM @{stageFileName} + FILE_FORMAT = (TYPE = CSV, PARSE_HEADER = TRUE) + MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE; + "; + await connection.ExecuteAsync(sqlCopy, transaction: transaction); + + var sqlMerge = $@" + MERGE INTO {tableName} AS T + USING {tempTableName} AS S + ON {string.Join(" AND ", keyColumns.Select(x => $"T.{x} = S.{x}"))} + WHEN MATCHED THEN + UPDATE SET + {string.Join(", ", valueColumns.Select(x => $"T.{x} = S.{x}"))}; + "; + await connection.ExecuteAsync(sqlMerge, transaction: transaction); + + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + finally + { + try + { + await this.ExecuteCommandAsync($"REMOVE @{stageFileName};"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to remove staged file {StageFileName} from Snowflake. Manual cleanup may be required.", stageFileName); + } + } + } + } + } + finally + { + if (File.Exists(tempFilePath)) + { + File.Delete(tempFilePath); + } + } + } + + private KeyValuePair GetRequestTypeQueryTag(string requestType) => new("Request_Type", requestType); + + private ILogger _logger { get; set; } + + public SnowflakeDatabaseContext(DbContextOptions options) + : base(options) + { + var loggerFactory = (ILoggerFactory)new LoggerFactory(); + _logger = loggerFactory.CreateLogger(); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SnowflakeDatabaseContextFactory.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SnowflakeDatabaseContextFactory.cs new file mode 100644 index 0000000..13a13cc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SnowflakeDatabaseContextFactory.cs @@ -0,0 +1,22 @@ +using Strata.ContinuousImprovement.Biz.ClientWarehouses; +using Strata.SnowflakeLib; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Snowflake +{ + public class SnowflakeDatabaseContextFactory : ISnowflakeDatabaseContextFactory + { + private readonly SnowflakeClientDbConnectionStringBuilderFactory _snowflakeClientDbConnectionStringBuilderFactory; + private readonly IClientWarehouseService _clientWarehouseService; + + public SnowflakeDatabaseContextFactory(SnowflakeClientDbConnectionStringBuilderFactory snowflakeClientDbConnectionStringBuilderFactory, IClientWarehouseService clientWarehouseService) + { + _snowflakeClientDbConnectionStringBuilderFactory = snowflakeClientDbConnectionStringBuilderFactory; + _clientWarehouseService = clientWarehouseService; + } + + public SnowflakeDatabaseContext Create(string databaseGuid) + { + return new SnowflakeDatabaseContext(databaseGuid, _snowflakeClientDbConnectionStringBuilderFactory, _clientWarehouseService); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SnowflakeExtensions.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SnowflakeExtensions.cs new file mode 100644 index 0000000..8e7c17c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SnowflakeExtensions.cs @@ -0,0 +1,12 @@ +using System.Text.RegularExpressions; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Snowflake +{ + public static class SnowflakeExtensions + { + public static string ConvertSqlToSnowflakeParameterSyntax(this string sql) + => Regex.Replace(sql.Replace("@", ":").Replace("--", ""), + @"IN \(\s*(:[^)]*)\s*\)", "IN $1", + RegexOptions.IgnoreCase | RegexOptions.Multiline); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SqlGridReaderWrapper.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SqlGridReaderWrapper.cs new file mode 100644 index 0000000..2c98470 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/Snowflake/SqlGridReaderWrapper.cs @@ -0,0 +1,31 @@ +using Dapper; +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.DbContexts.Snowflake +{ + public class SqlGridReaderWrapper : ISqlGridReaderWrapper + { + private readonly SqlMapper.GridReader _sqlReader; + + public SqlGridReaderWrapper(SqlMapper.GridReader sqlReader) + { + _sqlReader = sqlReader; + } + + public IEnumerable Read(bool buffered = true) + { + return _sqlReader.Read(buffered); + } + + public IEnumerable Read(bool buffered = true) + { + return _sqlReader.Read(buffered); + } + + public void Dispose() + { + _sqlReader?.Dispose(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/DbContexts/SqlFunctions.cs b/src/Strata.ContinuousImprovement.Biz/DbContexts/SqlFunctions.cs new file mode 100644 index 0000000..2c54ca4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/DbContexts/SqlFunctions.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.DbContexts +{ + public static class SqlFunctions + { + [DbFunction(Name = "STRING_SPLIT", IsBuiltIn = true)] + public static IQueryable Split(string source, string separator) + => throw new NotSupportedException("Only works in database queries."); + } + + [Keyless] + public class StringValue + { + [Column("value")] + public string Value { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/CCOpportunityDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/CCOpportunityDto.cs new file mode 100644 index 0000000..b63f793 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/CCOpportunityDto.cs @@ -0,0 +1,78 @@ +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + [ExcludeFromCodeCoverage] + public class CCOpportunityDto + { + // Save properties + [Required] + public Guid ConfigurationGuid { get; set; } + [Required] + [StringLength(200)] + public string Name { get; set; } + [Required] + public int EncounterGroup { get; set; } + public List EncounterGroups { get; set; } + public List ChargeCodes { get; set; } + public bool IsSeasonal { get; set; } + + [Required] + public int OpportunityLevel { get; set; } + public List OpportunityLevels { get; set; } + public DateTime BaselineStartDate { get; set; } + public DateTime BaselineEndDate { get; set; } + public DateTime TrackingStartDate { get; set; } + public DateTime TrackingEndDate { get; set; } + public bool IsHidden { get; set; } + public string Note { get; set; } = String.Empty; + + // Display properties + public int TotalCases { get; set; } + public double TotalCost => Details?.Sum(Details => Details.TotalCost) ?? 0; + public double GoalTotalCost => GetGoalTotalCost(); + public double ImprovementTotalCost => TotalCost - GoalTotalCost; + public double CostPerCase => TotalCost.DivideBy(TotalCases); + public double GoalCostPerCase => GoalTotalCost.DivideBy(TotalCases); + public double ImprovementCostPerCase => CostPerCase - GoalCostPerCase; + public string OpportunityKey { get; set; } + public long OpportunityId { get; set; } + public string SourceOpportunity { get; set; } + public int SourceOpportunityId { get; set; } + public IEnumerable Details { get; set; } + public IEnumerable LevelCases { get; set; } + public IEnumerable TreeGridData => Details?.GroupBy(detail => detail.OpportunityLevelId.ToString()) + .Select(opportunityLevelGrouping => new CCOpportunityDetailTreeGridModel(opportunityLevelGrouping, string.Empty, + opportunityLevelGrouping => opportunityLevelGrouping.FirstOrDefault()?.OpportunityLevelName ?? "System", + LevelCases, + TypeOfChildren.Rollup)) + ?? new List(); + + private double GetGoalTotalCost() + { + if (Details == null) return 0; + double goalTotalCost = 0; + + var detailsGroup = Details.GroupBy(d => new { d.OpportunityLevelId, d.ChargeCodeId }); + foreach (var groupedOpportunityDetails in detailsGroup) + { + var totalCost = groupedOpportunityDetails.Sum(data => data.TotalCost); + var totalUnitsOfService = groupedOpportunityDetails.Sum(data => data.TotalUnitsOfService); + var goalTotalUnitsOfService = groupedOpportunityDetails.Sum(data => data.GoalTotalUnitsOfService); + var goalCostPerUnit = groupedOpportunityDetails.First().GoalCostPerUnit; + var goalCost = goalCostPerUnit * goalTotalUnitsOfService; + + goalTotalCost += goalCost; + } + + return goalTotalCost; + } + } +} + diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/ClientNetworkOpportunityDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/ClientNetworkOpportunityDto.cs new file mode 100644 index 0000000..010b3f2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/ClientNetworkOpportunityDto.cs @@ -0,0 +1,67 @@ +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + public class ClientNetworkOpportunityDto + { + public int StrataId { get; set; } + public int NetworkOpportunityId { get; set; } + public int IdentifiedSavings { get; set; } + public string IdentifiedSavingsDescription { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool IsPublished { get; set; } + public int AnalyticsWorkbookId { get; set; } + public int WorkbookTemplateId { get; set; } + public InitiativeStatuses InitiativeStatus { get; set; } + + public IEnumerable Opportunities { get; set; } + + public ClientNetworkOpportunityDto() + { + Opportunities = new List(); + } + + public ClientNetworkOpportunityDto(ClientNetworkOpportunity clientNetworkOpportunity) + { + StrataId = clientNetworkOpportunity.StrataId; + IdentifiedSavings = clientNetworkOpportunity.IdentifiedSavings; + IdentifiedSavingsDescription = clientNetworkOpportunity.IdentifiedSavingsDescription; + NetworkOpportunityId = clientNetworkOpportunity.NetworkOpportunityId; + Name = clientNetworkOpportunity.NetworkOpportunity.Name; + Description = clientNetworkOpportunity.NetworkOpportunity.Description; + IsPublished = clientNetworkOpportunity.IsPublished; + AnalyticsWorkbookId = clientNetworkOpportunity.AnalyticsWorkbookId; + WorkbookTemplateId = clientNetworkOpportunity.NetworkOpportunity.WorkbookTemplateId; + InitiativeStatus = clientNetworkOpportunity.InitiativeStatus; + Opportunities = clientNetworkOpportunity.Opportunities?.Select(opp => new ClientOpportunity(opp)) + ?? new List(); + } + } + + public class ClientOpportunity + { + public Guid ConfigurationGuid { get; set; } + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public string Opportunity { get; set; } + public string CostDriver { get; set; } + public string Initiative { get; set; } + + public ClientOpportunity() { } + + public ClientOpportunity(ClientNetworkOpportunityLink opportunityLink) + { + ConfigurationGuid = opportunityLink.ConfigurationGuid; + OpportunityGuid = opportunityLink.OpportunityGuid; + InitiativeGuid = opportunityLink.InitiativeGuid; + Opportunity = opportunityLink.Opportunity; + CostDriver = opportunityLink.CostDriver; + Initiative = opportunityLink.Initiative; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/ENOpportunityDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/ENOpportunityDto.cs new file mode 100644 index 0000000..acfebd3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/ENOpportunityDto.cs @@ -0,0 +1,64 @@ +using Strata.ContinuousImprovement.Biz.Encounter; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + [ExcludeFromCodeCoverage] + public class ENOpportunityDto + { + [Required] + public Guid ConfigurationGuid { get; set; } + public Guid OpportunityGuid { get; set; } + [Required] + [StringLength(200)] + public string Name { get; set; } + [Required] + public int EncounterGroup { get; set; } + [Required] + public int OpportunityLevel { get; set; } + public string OpportunityKey { get; set; } + public long OpportunityId { get; set; } + public bool IsHidden { get; set; } + public string OpportunityLevelLabel { get; set; } + public List EncounterGroups { get; set; } + public List OpportunityLevels { get; set; } + public List EncounterGroupIds { get; set; } + public List OpportunityLevelIds { get; set; } + public double RevenueGoal { get; set; } + public double RevenueImprovement { get; set; } + public double CostGoal { get; set; } + public double CostImprovement { get; set; } + public double MarginGoal { get; set; } + public double MarginImprovement { get; set; } + public DateTime BaselineStartDate { get; set; } + public DateTime BaselineEndDate { get; set; } + public DateTime TrackingStartDate { get; set; } + public DateTime TrackingEndDate { get; set; } + public Dictionary> GridData { get; set; } + public string EncounterOpportunityJSON { get; set; } + + public bool IsSeasonal { get; set; } + + public bool IsBaseline { get; set; } = true; + public decimal BaselineValue; + public decimal GoalValue; + + public string OpportunityLevelName() + { + return OpportunityLevel switch + { + 1 => "Entity", + 2 => "ServiceLine", + 3 => "Specialty", + 4 => "Provider", + _ => "System" + }; + } + + } +} + diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/ExplorationPopulationDataDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/ExplorationPopulationDataDto.cs new file mode 100644 index 0000000..7bffa7e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/ExplorationPopulationDataDto.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + public class ExplorationPopulationDataDto + { + public long ExplorationPopulationId { get; set; } + public string Name { get; set; } + public int TotalEncounters { get; set; } + public decimal TotalVariation { get; set; } + public DateTime StartDischargeDate { get; set; } + public DateTime EndDischargeDate { get; set; } + public int AssociatedOpportunitiesCount { get; set; } + public int AssociatedInitiativesCount { get; set; } + public IEnumerable Opportunities { get; set; } + } + + public class ExplorationPopulationOpportunityDto + { + public long OpportunityId { get; set; } + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public string CostDriver { get; set; } + public long ExplorationPopulationId { get; set; } + public Guid ConfigurationGuid { get; set; } + public DateTime? BaselineStartDate { get; set; } + public DateTime? BaselineEndDate { get; set; } + public DateTime? TrackingStartDate { get; set; } + public DateTime? TrackingEndDate { get; set; } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/ExplorationPopulationDetailsDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/ExplorationPopulationDetailsDto.cs new file mode 100644 index 0000000..c8b93f5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/ExplorationPopulationDetailsDto.cs @@ -0,0 +1,23 @@ +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + public class ExplorationPopulationDetailsDto + { + public long ExplorationPopulationId { get; set; } + public string Name { get; set; } + public string CostDriver { get; set; } + public ExclusionCriteria ExclusionCriteria { get; set; } + public List Filters { get; set; } + public bool HasManuallyChangeEncounters { get; set; } + public List AssociatedOpportunities { get; set; } + public List AssociatedInitiatives { get; set; } + } + + public class ExplorationPopulationStrategicOpportunityDto(long opportunityId, string name) + { + public long OpportunityId { get; set; } = opportunityId; + public string Name { get; set; } = name; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/ExplorationPopulationDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/ExplorationPopulationDto.cs new file mode 100644 index 0000000..887c6ce --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/ExplorationPopulationDto.cs @@ -0,0 +1,15 @@ +using Strata.ContinuousImprovement.Biz.Population; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + public class ExplorationPopulationDto : ExplorationPopulation + { + public List ManuallyExcludedEncounterIds { get; set; } + public List ManuallyIncludedEncounterIds { get; set; } + public List MappingEncounterIds { get; set; } + public int OpportunitiesCount { get; set; } + public int? AssociatedOpportunitiesCount { get; set; } + public int? AssociatedInitiativesCount { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/FFOpportunityDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/FFOpportunityDto.cs new file mode 100644 index 0000000..438bd8e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/FFOpportunityDto.cs @@ -0,0 +1,25 @@ +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities; +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + [ExcludeFromCodeCoverage] + public class FFOpportunityDto + { + [Required] + public FinancialImprovementType ImprovementType { get; set; } + [Required] + public MetricType MetricType { get; set; } + [Required] + [StringLength(200)] + public string Name { get; set; } + [Required] + public int EntityId { get; set; } + public string MetricNumerator { get; set; } + public string MetricDenominator { get; set; } + [Required] + public Guid ConfigurationGuid { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/KeyValueDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/KeyValueDto.cs new file mode 100644 index 0000000..8073582 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/KeyValueDto.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + public class KeyValueDto + { + public TKey Key { get; set; } + public TValue Value { get; set; } + + public KeyValueDto() { } + public KeyValueDto(TKey key, TValue value) + { + Key = key; + Value = value; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/OpportunityJobResult.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/OpportunityJobResult.cs new file mode 100644 index 0000000..21e31bb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/OpportunityJobResult.cs @@ -0,0 +1,12 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Dtos + +{ + public class OpportunityJobResultDto + { + public Guid JobId { get; set; } + public Guid OpportunityGuid { get; set; } + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/OpportunityJobResultsDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/OpportunityJobResultsDto.cs new file mode 100644 index 0000000..da4c520 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/OpportunityJobResultsDto.cs @@ -0,0 +1,17 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + public class OpportunityJobResultsDto + { + public Guid JobId { get; set; } + public Guid OpportunityGuid { get; set; } + + public OpportunityJobResultsDto(Guid jobId, Guid opportunityGuid) + { + JobId = jobId; + OpportunityGuid = opportunityGuid; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Dtos/PROpportunityDto.cs b/src/Strata.ContinuousImprovement.Biz/Dtos/PROpportunityDto.cs new file mode 100644 index 0000000..5a97b32 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Dtos/PROpportunityDto.cs @@ -0,0 +1,40 @@ +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + +namespace Strata.ContinuousImprovement.Biz.Dtos +{ + [ExcludeFromCodeCoverage] + public class PROpportunityDto + { + [Required] + public Guid ConfigurationGuid { get; set; } + [Required] + public Guid OpportunityGuid { get; set; } + [Required] + public Guid InitiativeGuid { get; set; } + [Required] + [StringLength(200)] + public string Name { get; set; } + [Required] + public Guid OwnerGuid { get; set; } + public string OwnerName { get; set; } + public string Note { get; set; } + public string SourceOpportunity { get; set; } + public string OpportunityKey { get; set; } + public int SourceOpportunityId { get; set; } + public bool IsHidden { get; set; } + public bool HasInitiative { get; set; } + public long OpportunityId { get; set; } + [Required] + public PayrollGoalType PayrollGoal { get; set; } + public bool HasAllDepartments { get; set; } + public bool HasAllJobCodes { get; set; } + public bool HasAllPayCodes { get; set; } + public List Departments { get; set; } + public List JobCodes { get; set; } + public List PayCodes { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Email/EmailBuilder.cs b/src/Strata.ContinuousImprovement.Biz/Email/EmailBuilder.cs new file mode 100644 index 0000000..0c8dd98 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Email/EmailBuilder.cs @@ -0,0 +1,101 @@ +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Net.Http.Headers; +using System.Net.Http; +using System.Net.Mail; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Email +{ + public class EmailBuilder : IFluentEmailBuilder, IEmailBuilder, IDisposable + { + private readonly IHttpClientFactory _httpClientFactory; + + private readonly IOptions _option; + + private MultipartFormDataContent _emailContent; + + private bool _isDisposed; + + public EmailBuilder(IHttpClientFactory httpClientFactory, IOptions option) + { + _httpClientFactory = httpClientFactory; + _option = option; + _emailContent = new MultipartFormDataContent(); + } + + public IFluentEmailBuilder NewEmail() + { + if (_emailContent != null) + { + _emailContent.Dispose(); + } + + _emailContent = new MultipartFormDataContent(); + _isDisposed = false; + return this; + } + + public IFluentEmailBuilder From() + { + var fromAddress = new MailAddress(_option.Value.FromEmailAddress).ToString(); + + _emailContent.Add(new StringContent(fromAddress), "from"); + + return this; + } + + public IFluentEmailBuilder To(IList toAddresses) + { + foreach (var toAddress in toAddresses) + { + _emailContent.Add(new StringContent(toAddress), "to"); + } + + return this; + } + + public IFluentEmailBuilder WithSubject(string subject) + { + _emailContent.Add(new StringContent(subject), "subject"); + + return this; + } + + public IFluentEmailBuilder WithHtmlBody(string htmlBody) + { + _emailContent.Add(new StringContent(htmlBody), "html"); + + return this; + } + + public async Task SendAsync() + { + var httpClient = _httpClientFactory.CreateClient(); + + Uri baseUrl = new(_option.Value.BaseUrl); + + Uri baseAddress = new Uri(baseUrl, $"{_option.Value.Domain}/messages"); + + string encodedApiKey = Convert.ToBase64String(Encoding.ASCII.GetBytes($"api:{_option.Value.ApiKey}")); + + httpClient.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Basic", encodedApiKey); + + var result = await httpClient.PostAsync(baseAddress, _emailContent); + + return result; + } + + public void Dispose() + { + if (!_isDisposed) + { + _emailContent.Dispose(); + _isDisposed = true; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Email/EmailInfo.cs b/src/Strata.ContinuousImprovement.Biz/Email/EmailInfo.cs new file mode 100644 index 0000000..e743041 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Email/EmailInfo.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.Email +{ + public class EmailInfo + { + public string Subject { get; set; } = string.Empty; + public string Body { get; set; } = string.Empty; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Email/EmailNotificationService.cs b/src/Strata.ContinuousImprovement.Biz/Email/EmailNotificationService.cs new file mode 100644 index 0000000..3ebb81e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Email/EmailNotificationService.cs @@ -0,0 +1,70 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Utilities; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Email +{ + public class EmailNotificationService( + ILogger logger, + IConfiguration config, + IEmailBuilder emailBuilder) : IEmailNotificationService + { + private readonly IEmailBuilder _emailBuilder = emailBuilder; + + private readonly ILogger _logger = logger; + + public async Task SendEmailAsync(string recipientEmail, EmailInfo emailInfo) + { + try + { + if (recipientEmail.IsNullOrEmpty()) + { + _logger.LogError("No recipient found for email notification."); + return false; + } + + using var email = _emailBuilder.NewEmail() + .From() + .To(new List() { recipientEmail }) + .WithSubject(emailInfo.Subject) + .WithHtmlBody(emailInfo.Body); + + var result = await email.SendAsync(); + + if (!result.IsSuccessStatusCode) + { + _logger.LogError($"Error sending email"); + return false; + } + + return true; + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "Error sending email notification"); + return false; + } + } + + public string BuildEmailBody(string htmlPath, string appVersion) + { + var templateFilePath = StandardHtmlEmailGenerator.GetPathFromOutputDir(htmlPath); + var body = StandardHtmlEmailGenerator.GetEmailContentTemplate(templateFilePath); + + var endpoint = config.GetValue("endPoints:strataJazz"); + var loginUrl = $"{endpoint}{appVersion}/login.aspx"; + var logoImage = $"{endpoint}{appVersion}/Res/Images/Email/strata-logo-color.png"; + + body = body.Replace("[STRATA_LOGIN_URL]", loginUrl); + body = body.Replace("[STRATA_LOGO_IMAGE]", logoImage); + body = body.Replace("[STRATA_COPYRIGHT_YEAR]", (DateTime.Now.Year).ToString()); + + return body; + } + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Email/IEmailBuilder.cs b/src/Strata.ContinuousImprovement.Biz/Email/IEmailBuilder.cs new file mode 100644 index 0000000..59be182 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Email/IEmailBuilder.cs @@ -0,0 +1,7 @@ +namespace Strata.ContinuousImprovement.Biz.Email +{ + public interface IEmailBuilder + { + IFluentEmailBuilder NewEmail(); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Email/IEmailNotificationService.cs b/src/Strata.ContinuousImprovement.Biz/Email/IEmailNotificationService.cs new file mode 100644 index 0000000..87c797d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Email/IEmailNotificationService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Email +{ + public interface IEmailNotificationService + { + Task SendEmailAsync(string recipientEmail, EmailInfo emailInfo); + string BuildEmailBody(string htmlPath, string appVersion); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Email/IFluentEmailBuilder.cs b/src/Strata.ContinuousImprovement.Biz/Email/IFluentEmailBuilder.cs new file mode 100644 index 0000000..2fcb2e2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Email/IFluentEmailBuilder.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Email +{ + public interface IFluentEmailBuilder : IDisposable + { + IFluentEmailBuilder From(); + + IFluentEmailBuilder To(IList toAddresses); + + IFluentEmailBuilder WithSubject(string subject); + + IFluentEmailBuilder WithHtmlBody(string htmlBody); + + Task SendAsync(); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Email/MailGunOption.cs b/src/Strata.ContinuousImprovement.Biz/Email/MailGunOption.cs new file mode 100644 index 0000000..6069c45 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Email/MailGunOption.cs @@ -0,0 +1,11 @@ +namespace Strata.ContinuousImprovement.Biz.Email +{ + public class MailGunOption + { + public const string MailGun = "MailGun"; + public string ApiKey { get; set; } + public string BaseUrl { get; set; } + public string Domain { get; set; } + public string FromEmailAddress { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Email/StandardHTMLEmailGenerator.cs b/src/Strata.ContinuousImprovement.Biz/Email/StandardHTMLEmailGenerator.cs new file mode 100644 index 0000000..63a0a51 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Email/StandardHTMLEmailGenerator.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; + +namespace Strata.ContinuousImprovement.Biz.Email +{ + public class StandardHtmlEmailGenerator + { + public static string GetEmailContentTemplate(string filePath) + { + var htmlContent = string.Empty; + using (var sourceReader = File.OpenText(filePath)) + { + htmlContent += sourceReader.ReadToEnd(); + } + + return htmlContent; + } + + public static string GetPathFromOutputDir(string relativePath) + { + var baseDirectory = AppContext.BaseDirectory?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) ?? string.Empty; + var sanitizedRelativePath = (relativePath ?? string.Empty).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return Path.Combine(baseDirectory, sanitizedRelativePath); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/ENOpportunityDetailJob.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/ENOpportunityDetailJob.cs new file mode 100644 index 0000000..91edd34 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/ENOpportunityDetailJob.cs @@ -0,0 +1,209 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Hangfire.Filters; +using Strata.Id.Client; +using Strata.Schema.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Encounter +{ + public class ENOpportunityDetailJob + { + protected readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IFiscalMonthResolver _fiscalMonthResolver; + private readonly IHubContext _notificationHubContext; + private readonly IIdServiceClient _idServiceClient; + private readonly ILogger _logger; + private readonly ISchemaServiceClient _schemaServiceClient; + + public ENOpportunityDetailJob( + IAsyncDbContextFactory dbContextFactory, + IFiscalMonthResolver fiscalMonthResolver, + IHubContext notificationHubContext, + IIdServiceClient idServiceClient, + ILogger logger, + ISchemaServiceClient schemaServiceClient) + { + _dbContextFactory = dbContextFactory; + _fiscalMonthResolver = fiscalMonthResolver; + _notificationHubContext = notificationHubContext; + _idServiceClient = idServiceClient; + _logger = logger; + _schemaServiceClient = schemaServiceClient; + } + + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + [DeleteOnSuccess] + [JobDisplayName("Populate Encounter details for {1} on ({0})")] + [CaptureClaimsPrincipal] + [Semaphore(nameof(PopulateOpportunityDetailsAsync))] + public async Task PopulateOpportunityDetailsAsync(int strataId, string name, Guid opportunityGuid, Guid dbGuid, bool isUpdate, bool isTempoPage, CancellationToken cancellationToken) + { + await using var dbContext = await GetDbContext(cancellationToken); + dbContext.Database.SetCommandTimeout(TimeSpan.FromMinutes(30)); + + try + { + var hub = _notificationHubContext.Clients.Group(name); + if (isUpdate) + { + await hub.SendOpportunityUpdateDetails(name, 0); + } + else + { + await hub.SendOpportunityCreateDetails(name, 0); + } + + var opportunity = await dbContext.ENOpportunities.SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid); + if (opportunity == null) + { + throw new ArgumentException($"Opportunity Not Found for opportunty guid {opportunityGuid}", nameof(opportunityGuid)); + } + var configuration = await dbContext.Configurations.SingleOrDefaultAsync(c => c.ConfigurationGuid == opportunity.ConfigurationGuid, cancellationToken); + + configuration.FiscalMonthResolver = _fiscalMonthResolver; + var opportunityFilters = JsonConvert.DeserializeObject(opportunity.EncounterOpportunityJSON, + new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }); + var opportunityLevel = opportunityFilters.EncounterOpportunityLevel; + + if (isUpdate) + { + var existingMetricData = await dbContext.EncounterOpportunityMetricData.Where(data => data.OpportunityGUID == opportunityGuid).ToListAsync(); + dbContext.EncounterOpportunityMetricData.RemoveRange(existingMetricData); + } + + var oppMetrics = (await MetricData(opportunity, configuration)).ToList(); + var months = Enumerable.Range(1, 12).ToList(); + var monthlyMetrics = oppMetrics.SelectMany(metric => months.ConvertAll(month => new ENOpportunityMetricData(metric, month, true))).ToList(); + + var monthlyResults = await dbContext.GetENOpportunityDetailQuery(opportunity, configuration, _schemaServiceClient, _logger, dbGuid); + monthlyResults.ForEach(result => result.IsBaseline = true); + + monthlyMetrics.ForEach(metric => + metric.UpdateDataWithGoals(monthlyResults.Find(result => result.IsEquivalent(metric)))); + + monthlyResults.RemoveAll(result => monthlyMetrics.Exists(metric => result.IsEquivalent(metric))); + + var defaultNames = oppMetrics.GroupBy(r => r.LevelID) + .ToDictionary(g => g.Key, g => g.FirstOrDefault(o => o.LevelID == g.Key && !string.IsNullOrEmpty(o.Name))?.Name ?? string.Empty); + monthlyMetrics.AddRange(monthlyResults.ConvertAll(r => + r.EncounterMetricData(true, opportunityGuid, opportunityFilters.OpportunityLevel, defaultNames))); + + dbContext.EncounterOpportunityMetricData.AddRange(monthlyMetrics); + + await dbContext.SaveChangesAsync(); + + await hub.SendOpportunityCreateUpdateSuccess(opportunity.OpportunityGuid, isTempoPage, opportunity.Name, isUpdate, OpportunityTypes.Encounter); + } + catch (Exception ex) + { + var db = (await _idServiceClient.GetStrataIds()).SingleOrDefault(i => i.StrataId == strataId); + if (db == null) + { + _logger.LogContextError(ex, dbContext, nameof(PopulateOpportunityDetailsAsync), + new Dictionary + { + { nameof(strataId), strataId }, + { nameof(name), name }, + { nameof(opportunityGuid), opportunityGuid }, + { nameof(dbGuid), dbGuid }, + { nameof(isUpdate), isUpdate } + }); + } + else + { + _logger.LogContextError(ex, db, nameof(PopulateOpportunityDetailsAsync), + new Dictionary + { + { nameof(strataId), strataId }, + { nameof(name), name }, + { nameof(opportunityGuid), opportunityGuid }, + { nameof(dbGuid), dbGuid }, + { nameof(isUpdate), isUpdate } + }); + } + await _notificationHubContext.Clients.Group(name).SendOpportunityCreateError(name); + } + } + + internal class EncounterOpportunityMetricCohort + { + public int CohortID { get; set; } + public string CohortName { get; set; } + public EncounterOpportunityMetricCohort(int id, string name) + { + CohortID = id; + CohortName = name; + } + } + + private async Task> MetricData(ENOpportunityDataModel opportunity, Configuration.Configuration config) + { + var cohorts = new List(); + var opportunityFilters = JsonConvert.DeserializeObject(opportunity.EncounterOpportunityJSON, + new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }); + switch (opportunityFilters.OpportunityLevel) + { + case "Entity": + var entityDimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync(config.IsUsingCustomEntityDimension ? config.DepartmentRollupGlobalId : "Entity"); + var entities = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(entityDimension.DimensionGuid, + opportunityFilters.Entities.Select(sl => sl.Id)); + cohorts = entities.Select(e => new EncounterOpportunityMetricCohort(Int32.Parse(e.Id), e.Name)).ToList(); + break; + case "ServiceLine": + if (config.ServiceLineDimensionGuid != Guid.Empty) + { + var hierarchyElements = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(config.ServiceLineDimensionGuid, + opportunityFilters.ServiceLines.Select(sl => sl.Id)); + cohorts = hierarchyElements.Select(e => new EncounterOpportunityMetricCohort(Int32.Parse(e.Id), e.Name)).ToList(); + } + else + { + var dimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Service Line"); + var hierarchyElements = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, + opportunityFilters.ServiceLines.Select(sl => sl.Id)); + cohorts = hierarchyElements.Select(e => new EncounterOpportunityMetricCohort(Int32.Parse(e.Id), e.Name)).ToList(); + } + break; + case "Specialty": + var specialtyDimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync("DSS Physician Specialty"); + var specialties = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(specialtyDimension.DimensionGuid, + opportunityFilters.Specialties.Select(sl => sl.Id)); + cohorts = specialties.Select(e => new EncounterOpportunityMetricCohort(Int32.Parse(e.Id), e.Name)).ToList(); + break; + case "Provider": + var providerDimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Physician Primary"); + var providers = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(providerDimension.DimensionGuid, + opportunityFilters.Providers.Select(sl => sl.Id)); + cohorts = providers.Select(e => new EncounterOpportunityMetricCohort(Int32.Parse(e.Id), e.Name)).ToList(); + break; + default: + cohorts = new List { new EncounterOpportunityMetricCohort(0, string.Empty) }; + break; + } + return cohorts?.Select(c => new ENOpportunityMetricData(c.CohortID, opportunityFilters.OpportunityLevel, c.CohortName, opportunity.OpportunityGuid) { IsBaseline = true }).ToList(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/EncounterOpportunityMetric.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/EncounterOpportunityMetric.cs new file mode 100644 index 0000000..49fb327 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/EncounterOpportunityMetric.cs @@ -0,0 +1,54 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Encounter +{ + public class EncounterOpportunityMetric + { + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public Guid OpportunityGuid { get; set; } + public Guid AuthorGuid { get; set; } + public string AuthorName { get; set; } + public decimal BaselineValue; + public decimal GoalValue; + public int EncounterGroup { get; set; } + public bool IsSeasonal { get; set; } + public List CaseTypes { get; set; } + public List PatientPopulations { get; set; } + public string OpportunityLevel { get; set; } + + public OpportunityLevel EncounterOpportunityLevel => + Enum.TryParse(OpportunityLevel, true, out OpportunityLevel level) + ? level + : Shared.OpportunityLevel.Organization; + + public List Entities { get; set; } + public List ServiceLines { get; set; } + public List Specialties { get; set; } + public List Providers { get; set; } + + public bool IsBaseline { get; set; } = true; + + public EncounterOpportunityMetric() + { + Name = string.Empty; + OpportunityGuid = Guid.Empty; + EncounterGroup = 0; + OpportunityLevel = string.Empty; + CaseTypes = new List(); + PatientPopulations = new List(); + Entities = new List(); + ServiceLines = new List(); + Specialties = new List(); + Providers = new List(); + } + } + + public class FilterHelper + { + public string Text { get; set; } + public string Id { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/ENInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/ENInitiativeService.cs new file mode 100644 index 0000000..17e87f5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/ENInitiativeService.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Initiatives +{ + public class ENInitiativeService : InitiativeCreateService, IENInitiativeService + { + public ENInitiativeService( + IAsyncDbContextFactory dbContextFactory, + IInitiativeSavingsService initiativeSavingsService, + IAuthorizationServiceClient authorizationServiceClient, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger logger) + : base(dbContextFactory, initiativeSavingsService, authorizationServiceClient, updateFactOpportunitySavingsService, logger) + { + } + + public override async Task GetOpportunity(Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + return dbContext.EncounterOpportunities.SingleOrDefault(opp => opp.OpportunityGuid == opportunityGuid) as IOpportunity; + } + + public override async Task SaveOpportunity(Guid opportunityGuid, Guid initiativeGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunity = dbContext.EncounterOpportunities.SingleOrDefault(opp => opp.OpportunityGuid == opportunityGuid); + if (opportunity == null) { return; } + + opportunity.InitiativeGuid = initiativeGuid; + await dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateSavings(Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = await GetDbContext(cancellationToken); + var opportunity = await dbContext.Set().SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunity == null) { return false; } + if (opportunity.InitiativeGuid == Guid.Empty) { return true; } // no initiative to update + + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(init => init.InitiativeGUID == opportunity.InitiativeGuid, cancellationToken); + if (initiative == null) { return false; } + + var baselineMetrics = await dbContext.Set() + .Where(data => data.OpportunityGUID == opportunityGuid && data.IsBaseline).ToListAsync(cancellationToken); + opportunity.Savings = Math.Round(baselineMetrics.Sum(x => x.MarginImprovement), 2); + + await dbContext.SaveChangesAsync(cancellationToken); + return true; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/EncounterInitiativeCostType.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/EncounterInitiativeCostType.cs new file mode 100644 index 0000000..8260510 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/EncounterInitiativeCostType.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.Encounter.Initiatives +{ + public enum EncounterInitiativeCostType + { + EncounterSummary = 0, + CSF = 1, + DetailBilling = 2 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/EncounterInitiativeDto.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/EncounterInitiativeDto.cs new file mode 100644 index 0000000..45f5b47 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/EncounterInitiativeDto.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using Strata.ContinuousImprovement.Biz.Initiatives; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Initiatives +{ + public class EncounterInitiativeDto : InitiativeDto + { + public Guid ConfigurationGuid { get; set; } + public int EncounterGroup { get; set; } + public int OpportunityLevel { get; set; } + public List EncounterGroups { get; set; } + public List OpportunityLevels { get; set; } + public bool IsSeasonal { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/IENInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/IENInitiativeService.cs new file mode 100644 index 0000000..9623fc5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Initiatives/IENInitiativeService.cs @@ -0,0 +1,13 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Initiatives +{ + public interface IENInitiativeService : IInitiativeCreateService + { + Task UpdateSavings(Guid opportunityGuid, CancellationToken cancellationToken); + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunity.cs new file mode 100644 index 0000000..d846956 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunity.cs @@ -0,0 +1,20 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Shared; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + [Table("viewEncounterOpportunity", Schema = "cci")] + public class ENOpportunity : Opportunity + { + public string Name { get; set; } + public double Cases { get; set; } + [JsonConverter(typeof(JsonStringEnumConverter))] + public EncounterGroup EncounterGroup { get; set; } + public string EncounterGroupValue { get; set; } + public string OpportunityLevel { get; set; } + public bool IsSeasonal { get; set; } + public long OpportunityId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityDataModel.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityDataModel.cs new file mode 100644 index 0000000..a140f2f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityDataModel.cs @@ -0,0 +1,60 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Mappers; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + [Table("EncounterOpportunity", Schema = "cci")] + public class ENOpportunityDataModel : IOpportunity, IWithAuthor + { + [Key] + public Guid OpportunityGuid { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long OpportunityId { get; set; } + public string EncounterOpportunityJSON { get; set; } + public string Name { get; set; } + public DateTime DateCreated { get; set; } + public DateTime EndDate { get; set; } + public byte OpportunityType { get; set; } + public Guid OwnerGuid { get; set; } + public string OwnerName { get; set; } = string.Empty; + public Guid AuthorGuid { get; set; } + public string AuthorName { get; set; } + public Guid ConfigurationGuid { get; set; } + public double Savings { get; set; } + [NotMapped] + public double IdentifiedSavings { get; set; } + public Guid InitiativeGuid { get; set; } + public bool HasInitiative => InitiativeGuid != Guid.Empty; + [NotMapped] + public string Initiative { get; set; } + public bool IsHidden { get; set; } + public string Note { get; set; } = String.Empty; + + [NotMapped] + public string SourceOpportunity { get; set; } = ""; + [NotMapped] + public long SourceOpportunityId { get; set; } = -1; + + public virtual void Update(IOpportunity updates) + { + IdentifiedSavings = updates.IdentifiedSavings; + InitiativeGuid = updates.InitiativeGuid; + IsHidden = updates.IsHidden; + Note = updates.Note; + } + + // IOpportunity properties needed to implement interface + [NotMapped] + public string OpportunityKey + { + get + { + return $"EN - {OpportunityId}"; + } + set { } + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityDataModelMapper.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityDataModelMapper.cs new file mode 100644 index 0000000..d90b65e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityDataModelMapper.cs @@ -0,0 +1,164 @@ +using Newtonsoft.Json; +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Encounter.Initiatives; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class ENOpportunityDataModelMapper + { + public static ENOpportunityDataModelWithFilters MapToDataModel(this EncounterInitiativeDto src, ClaimsPrincipal user, bool isCreated = true) + { + var mapped = Mapped(src); + if (mapped == null) return default; + if (!isCreated) mapped.OpportunityGuid = src.OpportunityGuid; + mapped.MapAuthor(user); + mapped.OpportunityLevel = Enum.GetName(typeof(OpportunityLevel), src.OpportunityLevel); + if (src.EncounterGroup == (int)EncounterGroup.CaseType) + { + mapped.CaseTypes = src.EncounterGroups.MapToFilterHelpers(); + } + else + { + mapped.PatientPopulations = src.EncounterGroups.MapToFilterHelpers(); + } + switch (src.OpportunityLevel) + { + case (int)OpportunityLevel.Entity: + mapped.Entities = src.OpportunityLevels.MapToFilterHelpers(); + break; + case (int)OpportunityLevel.ServiceLine: + mapped.ServiceLines = src.OpportunityLevels.MapToFilterHelpers(); + break; + case (int)OpportunityLevel.Specialty: + mapped.Specialties = src.OpportunityLevels.MapToFilterHelpers(); + break; + case (int)OpportunityLevel.Provider: + mapped.Providers = src.OpportunityLevels.MapToFilterHelpers(); + break; + } + return mapped; + } + + public static ENOpportunityDataModelWithFilters MapToDataModel(this ENOpportunityDto src, ClaimsPrincipal user, bool isCreated = true) + { + var mapped = Mapped(src); + if (mapped == null) return default; + if (!isCreated) mapped.OpportunityGuid = src.OpportunityGuid; + mapped.MapAuthor(user); + mapped.OpportunityLevel = Enum.GetName(typeof(OpportunityLevel), src.OpportunityLevel); + if (src.EncounterGroup == (int)EncounterGroup.CaseType) + { + mapped.CaseTypes = isCreated ? src.EncounterGroups.MapToFilterHelpers() : src.EncounterGroupIds.MapToFilterHelpers(); + } + else + { + mapped.PatientPopulations = isCreated ? src.EncounterGroups.MapToFilterHelpers() : src.EncounterGroupIds.MapToFilterHelpers(); + } + switch (src.OpportunityLevel) + { + case (int)OpportunityLevel.Entity: + mapped.Entities = isCreated ? src.OpportunityLevels.MapToFilterHelpers() : src.OpportunityLevelIds.MapToFilterHelpers(); + break; + case (int)OpportunityLevel.ServiceLine: + mapped.ServiceLines = isCreated ? src.OpportunityLevels.MapToFilterHelpers() : src.OpportunityLevelIds.MapToFilterHelpers(); + break; + case (int)OpportunityLevel.Specialty: + mapped.Specialties = isCreated ? src.OpportunityLevels.MapToFilterHelpers() : src.OpportunityLevelIds.MapToFilterHelpers(); + break; + case (int)OpportunityLevel.Provider: + mapped.Providers = isCreated ? src.OpportunityLevels.MapToFilterHelpers() : src.OpportunityLevelIds.MapToFilterHelpers(); + break; + } + return mapped; + } + + public static ENOpportunityDataModel MapToDataModel(this ENOpportunityDataModelWithFilters src, ClaimsPrincipal user) + { + var mapped = Mapped(src); + if (mapped == null) return default; + mapped.MapAuthor(user); + mapped.EncounterOpportunityJSON = System.Text.Json.JsonSerializer.Serialize((ENOpportunityBaseFilters)src); + return mapped; + } + + public static ENOpportunityDto MapToDto(this ENOpportunityDataModel src) + { + var mapped = Mapped(src); + if (mapped == null) { return default; } + if (!string.IsNullOrEmpty(src.EncounterOpportunityJSON)) + { + var opportunityInfo = JsonConvert.DeserializeObject(src.EncounterOpportunityJSON, new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }); + mapped.EncounterGroup = opportunityInfo.EncounterGroup; + mapped.EncounterGroups = opportunityInfo.EncounterGroup == (int)EncounterGroup.CaseType + ? opportunityInfo.CaseTypes.Select(ct => ct.Text).ToList() : opportunityInfo.PatientPopulations.Select(ct => ct.Text).ToList(); + mapped.EncounterGroupIds = opportunityInfo.EncounterGroup == (int)EncounterGroup.CaseType + ? opportunityInfo.CaseTypes.Select(ct => ct.Id).ToList() : opportunityInfo.PatientPopulations.Select(ct => ct.Id).ToList(); + mapped.OpportunityLevelIds = GetOpportunityLevels(opportunityInfo); + mapped.IsSeasonal = opportunityInfo.IsSeasonal; + mapped.OpportunityLevel = (int)opportunityInfo.EncounterOpportunityLevel; + } + return mapped; + } + + public static ENOpportunitySaveModel MapToSaveModel(this ENOpportunityDto src) + { + var mapped = MapDtoToSaveModel(src); + if (mapped == null) { return default; } + var opportunityInfo = JsonConvert.DeserializeObject(src.EncounterOpportunityJSON, new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }); + + mapped.EncounterGroup = opportunityInfo.EncounterGroup; + mapped.IsSeasonal = opportunityInfo.IsSeasonal; + mapped.OpportunityLevel = (int)Enum.Parse(opportunityInfo.OpportunityLevel, true); + mapped.EncounterGroups = opportunityInfo.EncounterGroup == (int)EncounterGroup.CaseType + ? opportunityInfo.CaseTypes.Select(ct => ct.Id).ToList() + : opportunityInfo.PatientPopulations.Select(ct => ct.Id).ToList(); + mapped.OpportunityLevels = mapped.OpportunityLevel == (int)OpportunityLevel.Organization + ? new List { "System" } + : opportunityInfo.Entities.Select(ct => ct.Id).ToList(); + mapped.OpportunityLevelLabel = opportunityInfo.OpportunityLevel; + mapped.OpportunityLevels = GetOpportunityLevels(opportunityInfo); + + return mapped; + } + + public static partial EncounterOpportunityMetric MapFromDataModel(this ENOpportunityDataModelWithFilters src); + private static partial ENOpportunityDataModel Mapped(this ENOpportunityDataModelWithFilters src); + private static partial ENOpportunityDto Mapped(this ENOpportunityDataModel src); + private static partial ENOpportunitySaveModel MapDtoToSaveModel(this ENOpportunityDto src); + + [MapperIgnoreTarget(nameof(ENOpportunityDataModelWithFilters.OpportunityGuid))] + [MapperIgnoreTarget(nameof(ENOpportunityDataModelWithFilters.OpportunityLevel))] + [MapProperty(nameof(EncounterInitiativeDto.OpportunityLevel), nameof(ENOpportunityDataModelWithFilters.EncounterOpportunityLevel))] + private static partial ENOpportunityDataModelWithFilters Mapped(EncounterInitiativeDto src); + + [MapperIgnoreTarget(nameof(ENOpportunityDataModelWithFilters.OpportunityGuid))] + [MapProperty(nameof(ENOpportunityDto.OpportunityLevel), nameof(ENOpportunityDataModelWithFilters.EncounterOpportunityLevel))] + private static partial ENOpportunityDataModelWithFilters Mapped(ENOpportunityDto src); + + private static List GetOpportunityLevels(ENOpportunityBaseFilters opportunityInfo) + { + switch (opportunityInfo.EncounterOpportunityLevel) + { + case OpportunityLevel.Entity: + return opportunityInfo.Entities.Select(ct => ct.Id).ToList(); + case OpportunityLevel.ServiceLine: + return opportunityInfo.ServiceLines.Select(ct => ct.Id).ToList(); + case OpportunityLevel.Specialty: + return opportunityInfo.Specialties.Select(ct => ct.Id).ToList(); + case OpportunityLevel.Provider: + return opportunityInfo.Providers.Select(ct => ct.Id).ToList(); + default: + return default; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityDataModelWithFilters.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityDataModelWithFilters.cs new file mode 100644 index 0000000..7d6722f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityDataModelWithFilters.cs @@ -0,0 +1,29 @@ +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + public class ENOpportunityDataModelWithFilters : ENOpportunityBaseFilters, IWithAuthor + { + public Guid OpportunityGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public DateTime DateCreated { get; set; } + public Guid AuthorGuid { get; set; } + public string AuthorName { get; set; } + + public ENOpportunityDataModelWithFilters() + { + OpportunityGuid = Guid.NewGuid(); + CaseTypes = new List(); + PatientPopulations = new List(); + Entities = new List(); + ServiceLines = new List(); + Specialties = new List(); + Providers = new List(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityMetricData.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityMetricData.cs new file mode 100644 index 0000000..af64fb3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityMetricData.cs @@ -0,0 +1,88 @@ +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + [Table("EncounterOpportunityMetricData", Schema = "cci")] + public class ENOpportunityMetricData + { + [Key] + public Guid EncounterOpportunityMetricDataGUID { get; set; } + public Guid OpportunityGUID { get; set; } + public int LevelID { get; set; } + public int Month { get; set; } + public string Level { get; set; } + public string Name { get; set; } + public double Volume { get; set; } + public bool IsBaseline { get; set; } + public double Revenue { get; set; } + public double Cost { get; set; } + public double Margin { get; set; } + public double GoalVolume { get; set; } + public double GoalRevenuePerCase { get; set; } + public double GoalCostPerCase { get; set; } + public double GoalRevenue => GoalRevenuePerCase * GoalVolume; + public double RevenueImprovement => GoalRevenue - Revenue; + public double GoalCost => GoalCostPerCase * GoalVolume; + public double CostImprovement => Cost - GoalCost; + public double GoalMargin => GoalRevenue - GoalCost; + public double MarginImprovement => GoalMargin - Margin; + public double GoalMarginPerCase => GoalVolume == 0 + ? GoalRevenuePerCase - GoalCostPerCase + : GoalMargin.DivideBy(GoalVolume); + + + [NotMapped] + public double RevenuePerCase => Revenue.DivideBy(Volume); + + [NotMapped] + public double CostPerCase => Cost.DivideBy(Volume); + + [NotMapped] + public double MarginPerCase => Margin.DivideBy(Volume); + + public ENOpportunityMetricData(int id, string level, string name, Guid oppGuid) + { + OpportunityGUID = oppGuid; + LevelID = id; + Level = level; + Name = name; + Volume = 0; + GoalVolume = 0; + Revenue = 0; + GoalRevenuePerCase = 0; + Cost = 0; + GoalCostPerCase = 0; + Margin = 0; + IsBaseline = false; + } + + public ENOpportunityMetricData() { } + public ENOpportunityMetricData(ENOpportunityMetricData metric, int month, bool isBaseline) + { + Month = month; + IsBaseline = true; + Name = metric.Name; + Level = metric.Level; + LevelID = metric.LevelID; + OpportunityGUID = metric.OpportunityGUID; + } + + public void UpdateDataWithGoals(OpportunityMetricCohortData result, bool isBaseline = true) + { + if (result == null) return; + Volume = result.Volume; + Revenue = (double)result.Revenue; + Cost = (double)result.Cost; + Margin = (double)result.Margin; + GoalVolume = result.Volume; + GoalRevenuePerCase = (double)result.RevenuePerCase; + GoalCostPerCase = (double)result.CostPerCase; + IsBaseline = isBaseline; + Month = result.Month; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityRowData.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityRowData.cs new file mode 100644 index 0000000..3f3d099 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityRowData.cs @@ -0,0 +1,78 @@ +using Strata.ContinuousImprovement.Biz.Utilities; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + public class ENOpportunityRowData + { + public string Text { get; set; } + public int LevelId { get; set; } + public double Volume { get; set; } + public double RevenuePerCase { get; set; } + public double RevenueTotal { get; set; } + public double CostPerCase { get; set; } + public double CostTotal { get; set; } + public double MarginPerCase { get; set; } + public double MarginTotal { get; set; } + + public ENOpportunityRowData() { } + + public static KeyValuePair> KeyValuePair(IGrouping metricGroup) + => new KeyValuePair>( + string.IsNullOrEmpty(metricGroup.First().Name) ? "System" : metricGroup.First().Name, + GridData(metricGroup) + ); + + public static List GridData(IGrouping metricGroup) + => new List + { + BaselineData(metricGroup), + GoalData(metricGroup) + }; + + public static ENOpportunityRowData BaselineData(IGrouping metricGroup) + { + return new ENOpportunityRowData + { + Text = "Baseline", + LevelId = metricGroup.Key, + Volume = metricGroup.Sum(m => m.Volume), + RevenuePerCase = metricGroup.WeightedAverage(m => m.RevenuePerCase, m => m.Volume), + RevenueTotal = metricGroup.Sum(m => m.Revenue), + CostPerCase = metricGroup.WeightedAverage(m => m.CostPerCase, m => m.Volume), + CostTotal = metricGroup.Sum(m => m.Cost), + MarginPerCase = metricGroup.WeightedAverage(m => m.MarginPerCase, m => m.Volume), + MarginTotal = metricGroup.Sum(m => m.Margin) + }; + } + + public static ENOpportunityRowData GoalData(IGrouping metricGroup) + { + var baselineData = new ENOpportunityRowData + { + Text = "Goal", + LevelId = metricGroup.Key, + Volume = metricGroup.Sum(m => m.GoalVolume), + RevenuePerCase = metricGroup.WeightedAverage(m => m.GoalRevenuePerCase, m => m.Volume), + RevenueTotal = metricGroup.Sum(m => m.GoalRevenue), + CostPerCase = metricGroup.WeightedAverage(m => m.GoalCostPerCase, m => m.Volume), + CostTotal = metricGroup.Sum(m => m.GoalCost), + MarginPerCase = metricGroup.WeightedAverage(m => m.GoalMarginPerCase, m => m.Volume), + MarginTotal = metricGroup.Sum(m => m.GoalMargin) + }; + + if ((baselineData.RevenueTotal != 0 || metricGroup.Sum(m => m.GoalRevenuePerCase) != 0) && baselineData.RevenuePerCase == 0) + baselineData.RevenuePerCase = metricGroup.First(m => m.GoalRevenuePerCase != 0).GoalRevenuePerCase; + if ((baselineData.CostTotal != 0 || metricGroup.Sum(m => m.GoalCostPerCase) != 0) && baselineData.CostPerCase == 0) + baselineData.CostPerCase = metricGroup.First(m => m.GoalCostPerCase != 0).GoalCostPerCase; + if ((baselineData.MarginTotal != 0 || metricGroup.Sum(m => m.GoalMarginPerCase) != 0) && baselineData.MarginPerCase == 0) + { + baselineData.MarginPerCase = baselineData.Volume == 0 + ? baselineData.RevenuePerCase - baselineData.CostPerCase + : metricGroup.First(m => m.GoalMarginPerCase != 0).GoalMarginPerCase; + } + return baselineData; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityRowDataChanges.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityRowDataChanges.cs new file mode 100644 index 0000000..4d87de9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityRowDataChanges.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + public class ENOpportunityRowDataChanges + { + public int LevelId { get; set; } + public double Volume { get; set; } + public double RevenuePerCase { get; set; } + public double CostPerCase { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunitySaveModel.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunitySaveModel.cs new file mode 100644 index 0000000..97b0909 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunitySaveModel.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + public class ENOpportunitySaveModel + { + public Guid OpportunityGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public bool IsSeasonal { get; set; } + public string EncounterOpportunityJSON { get; set; } + public int OpportunityLevel { get; set; } + public string OpportunityLevelLabel { get; set; } + public string OpportunityLevelJson { get; set; } + + [Required] + public int EncounterGroup { get; set; } + + [NotMapped] + public bool IsFromWizard { get; set; } + [NotMapped] + public IEnumerable EncounterGroups { get; set; } + [NotMapped] + public IEnumerable OpportunityLevels { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityService.cs new file mode 100644 index 0000000..92a8e21 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/ENOpportunityService.cs @@ -0,0 +1,451 @@ +using ClosedXML.Excel; +using Hangfire; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using MoreLinq; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.Hangfire.Jazz.Client; +using Strata.Id.Client; +using Strata.Schema.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + public class ENOpportunityService : OpportunityService, IENOpportunityService + { + private readonly IJazzHangfireServiceClient _hangfireServiceClient; + private readonly ISchemaServiceClient _schemaServiceClient; + private readonly IConfigurationService _configurationService; + private readonly IFiscalMonthResolver _fiscalMonthResolver; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly IAuthorizationServiceClient _authorizationServiceClient; + private readonly IIdServiceClient _idServiceClient; + private readonly IHubContext _notificationHubContext; + + internal ENOpportunityService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IInitiativeService initiativeService, IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) + { + } + + public ENOpportunityService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + ISchemaServiceClient schemaServiceClient, + IJazzHangfireServiceClient hangfireServiceClient, + IConfigurationService configurationService, + IFiscalMonthResolver fiscalMonthResolver, + IInitiativeService initiativeService, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + IBackgroundJobClient backgroundJobClient, + IIdServiceClient idServiceClient, + IHubContext notificationHubContext, + IAuthorizationServiceClient authorizationServiceClient) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) + { + _hangfireServiceClient = hangfireServiceClient; + _schemaServiceClient = schemaServiceClient; + _configurationService = configurationService; + _fiscalMonthResolver = fiscalMonthResolver; + _backgroundJobClient = backgroundJobClient; + _idServiceClient = idServiceClient; + _notificationHubContext = notificationHubContext; + _authorizationServiceClient = authorizationServiceClient; + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return null; + + var opportunityContext = dbContext.Set(); + var configuredOpportunities = opportunityContext.Where(en => en.ConfigurationGuid == configurationGuid); + + var opportunityLevels = configuredOpportunities +#pragma warning disable S2971 // "IEnumerable" LINQs should be simplified + .Select(en => en.OpportunityLevel).Distinct().ToList() +#pragma warning restore S2971 // "IEnumerable" LINQs should be simplified + .Select(ol => new FilterMember { Id = ol, Text = (ol == "ServiceLine" ? "Service Line" : ol) }) + .OrderBy(ol => ol.Text); + + return new ENFilterOptions + { + OpportunityLevels = opportunityLevels + }; + } + + public async Task CreateAsync(ENOpportunityDataModel opportunity, Guid databaseGuid, bool isTempoPage, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(opportunity.Name); + try + { + await using var dbContext = await GetDbContext(cancellationToken); + opportunity.DateCreated = DateTime.Now; + var config = dbContext.Configurations.SingleOrDefault(c => c.ConfigurationGuid == opportunity.ConfigurationGuid); + + if (config == null) return default; + opportunity.EndDate = config.GetBaselineEndDate(OpportunityTypes.Encounter); + opportunity.OpportunityType = Convert.ToByte(OpportunityTypes.Encounter); + + dbContext.ENOpportunities.Add(opportunity); + + await dbContext.SaveChangesAsync(cancellationToken); + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(opportunity.OpportunityGuid, SecurableEntityType.Opportunity, cancellationToken); + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + + var jobId = _backgroundJobClient.Enqueue(job + => job.PopulateOpportunityDetailsAsync(strataId, opportunity.Name, opportunity.OpportunityGuid, databaseGuid, false, isTempoPage, cancellationToken)); + return Guid.Parse(jobId); + } + catch (Exception ex) + { + _logger.LogOpportunityError(ex, opportunity.Name, opportunity.OpportunityGuid); + await hub.SendOpportunityCreateError(opportunity.Name, ex.Message); + return default; + } + } + + internal async Task> GetEntityNodes(Configuration.Configuration configuration, CancellationToken cancellationToken) + { + var globalId = configuration.IsUsingCustomEntityDimension ? configuration.DepartmentRollupGlobalId : "Entity"; + var dimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync(globalId); + return await GetNodesByHierarchyGuid(dimension.DefaultHierarchyGuid, cancellationToken); + } + + public async Task GetENOpportunityById(Guid opportunityGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var opportunity = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .FirstOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunity == null) { return null; } + var config = dbContext.Configurations.SingleOrDefault(c => c.ConfigurationGuid == opportunity.ConfigurationGuid); + + if (config == null) { return new ENOpportunityDto(); } + config.FiscalMonthResolver = _fiscalMonthResolver; + + if (opportunity is IWithSourceOpportunity && _centralDbContext != null) + { + try + { + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities) + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(cno => cno.Opportunities.Any(link => link.OpportunityGuid == opportunity.OpportunityGuid)); + ((IWithSourceOpportunity)opportunity).SetSourceOpportunity(clientNetworkOpportunity); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, $"Failed to get source opportunity for {opportunityGuid}"); + throw; + } + } + + var metricData = dbContext.Set().AsQueryable().Where(data => data.OpportunityGUID == opportunity.OpportunityGuid && data.IsBaseline).ToList(); + var oppotunityDto = opportunity.MapToDto(); + oppotunityDto.RevenueGoal = metricData.Sum(x => x.GoalRevenue); + oppotunityDto.RevenueImprovement = metricData.Sum(x => x.RevenueImprovement); + oppotunityDto.CostGoal = metricData.Sum(x => x.GoalCost); + oppotunityDto.CostImprovement = metricData.Sum(x => x.CostImprovement); + oppotunityDto.MarginGoal = metricData.Sum(x => x.GoalMargin); + oppotunityDto.MarginImprovement = metricData.Sum(x => x.MarginImprovement); + oppotunityDto.GridData = GetOpportnuityGridData(metricData); + oppotunityDto.BaselineStartDate = config.GetBaselineStartDate(OpportunityTypes.Encounter); + oppotunityDto.BaselineEndDate = config.GetBaselineEndDate(OpportunityTypes.Encounter); + oppotunityDto.TrackingStartDate = config.GetTrackingStartDate(OpportunityTypes.Encounter); + oppotunityDto.TrackingEndDate = config.GetTrackingEndDate(); + return oppotunityDto; + } + + public async Task GetInitiativeInfoAsync(Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) { return default; } + var opportunityItem = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunityItem == null) { return default; } + var configuration = await _configurationService.GetConfigurationAsync(opportunityItem.ConfigurationGuid, cancellationToken); + + if (opportunityItem.InitiativeGuid != Guid.Empty) + { + var initiative = dbContext?.Initiatives.SingleOrDefault(i => i.InitiativeGUID == opportunityItem.InitiativeGuid); + var fiscalMonths = configuration.GetTrackingFiscalMonths(OpportunityTypes.OrgDefined, initiative.EffectiveDate); + return initiative?.InitiativeInfo(opportunityItem.OpportunityGuid, fiscalMonths, configuration.OrgDefinedTrackingStartDate, configuration.OrgDefinedTrackingEndDate); + } + var info = new InitiativeInfo + { + OpportunityGuid = opportunityGuid, + Name = opportunityItem.Name, + CostDriver = "Encounter", + StartMonth = configuration.OrgDefinedTrackingStartDate.ToString("yyyy-MM-dd"), + EndMonth = configuration.OrgDefinedTrackingEndDate.ToString("yyyy-MM-dd") + }; + return info; + } + + public async Task UpdateAsync(Guid databaseGuid, ENOpportunityDataModelWithFilters opportunityWithFilters, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(opportunityWithFilters.Name); + try + { + await using var dbContext = await GetDbContext(cancellationToken); + + // do not use NotFoundException from Strata.CoreLib.Excpetions as it leads to the error + var originalOpportunity = dbContext.ENOpportunities + .FirstOrDefault(opp => opp.OpportunityGuid.Equals(opportunityWithFilters.OpportunityGuid)) + ?? throw new Exception($"Encounter Opportunity with guid {opportunityWithFilters.OpportunityGuid} is not found"); + + await SetOpportunityFilters(opportunityWithFilters, cancellationToken); + originalOpportunity.Name = opportunityWithFilters.Name; + var previousOpportunityJSON = originalOpportunity.EncounterOpportunityJSON; + originalOpportunity.EncounterOpportunityJSON = System.Text.Json.JsonSerializer.Serialize((ENOpportunityBaseFilters)opportunityWithFilters); + + await dbContext.SaveChangesAsync(cancellationToken); + + if (previousOpportunityJSON != originalOpportunity.EncounterOpportunityJSON) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + + originalOpportunity.IdentifiedSavings = 0; + originalOpportunity.Savings = 0; + await dbContext.SaveChangesAsync(cancellationToken); + + var jobId = _backgroundJobClient.Enqueue(job + => job.PopulateOpportunityDetailsAsync(strataId, originalOpportunity.Name, originalOpportunity.OpportunityGuid, databaseGuid, true, true, cancellationToken)); + return jobId; + } + + await hub.SendOpportunityCreateUpdateSuccess(originalOpportunity.OpportunityGuid, true, originalOpportunity.Name, true, OpportunityTypes.Encounter); + return Guid.Empty.ToString(); + } + catch (Exception ex) + { + _logger.LogOpportunityError(ex, opportunityWithFilters.Name, opportunityWithFilters.OpportunityGuid); + await hub.SendOpportunityUpdateError(opportunityWithFilters.Name); + return null; + } + } + + public async Task SetOpportunityFilters(ENOpportunityDataModelWithFilters filters, CancellationToken cancellationToken) + { + var configuration = await _configurationService.GetConfigurationAsync(filters.ConfigurationGuid, cancellationToken); + filters.IsBaseline = true; + List filterHelpers; + IEnumerable ids; + if (filters.EncounterGroup == 0) + { + var nonPatientPopDimension = Guid.Parse("7799FFBA-B233-4E72-A629-D0BBA4082F34"); + var hierarchyPaths = filters.CaseTypes.Select(ct => ct.Id); + filterHelpers = await GetNodesByHierarchyPath(nonPatientPopDimension, hierarchyPaths, cancellationToken); + ids = filters.CaseTypes.Select(x => x.Id); + filters.CaseTypes = filterHelpers.Where(ct => ids.Contains(ct.Id)).ToList(); + } + else + { + filterHelpers = await GetNodesByGlobalId("Clinical Indicator", cancellationToken); + ids = filters.PatientPopulations.Select(x => x.Id); + filters.PatientPopulations = + filterHelpers.Where(pp => ids.Contains(pp.Id)).ToList(); + } + + switch (filters.EncounterOpportunityLevel) + { + case OpportunityLevel.Entity: + filterHelpers = await GetEntityNodes(configuration, cancellationToken); + ids = filters.Entities.Select(x => x.Id); + filters.Entities = filterHelpers.Where(e => ids.Contains(e.Id)).ToList(); + break; + case OpportunityLevel.ServiceLine: + filterHelpers = await GetServiceLineNodes(configuration, filters.ServiceLines.Select(x => x.Id), cancellationToken); + ids = filters.ServiceLines.Select(x => x.Id); + filters.ServiceLines = filterHelpers.Where(e => ids.Contains(e.Id)).ToList(); + break; + case OpportunityLevel.Specialty: + filterHelpers = await GetNodesByGlobalId("DSS Physician Specialty", cancellationToken); + ids = filters.Specialties.Select(x => x.Id); + filters.Specialties = filterHelpers.Where(e => ids.Contains(e.Id)).ToList(); + break; + case OpportunityLevel.Provider: + filterHelpers = await GetNodesByHierarchyPath(new Guid("8944429C-0164-4107-870E-EDE98A059324"), filters.Providers.Select(ct => ct.Id), cancellationToken); + ids = filters.Providers.Select(x => x.Id); + filters.Providers = filterHelpers.Where(e => ids.Contains(e.Id)).ToList(); + break; + } + } + + public async Task UpdateGoalsAsync(Guid opportunityGuid, IEnumerable changes, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return false; + + var changedMetrics = new List(); + + foreach (var change in changes) + { + var metrics = UpdateGoal(dbContext, opportunityGuid, change, cancellationToken); + changedMetrics.AddRange(metrics); + } + + dbContext.EncounterOpportunityMetricData.UpdateRange(changedMetrics); + var changesNumber = await dbContext.SaveChangesAsync(cancellationToken); + return changesNumber > 0; + } + + public async Task ExportAsync(SortOptions sortOptions, ENFilters filters, CancellationToken cancellationToken) + { + var exportOptions = this.GetExportOptions(); + + // get data + var opportunities = await this.GetAllAsync(sortOptions, filters, cancellationToken); + var data = opportunities.Select(opp => new + { + Opportunity = opp.OpportunityKey, + opp.Name, + opp.IdentifiedSavings, + opp.Cases, + EncounterGroup = opp.EncounterGroupValue, + Level = opp.OpportunityLevel, + Seasonality = opp.IsSeasonal ? "With" : "Without", + opp.Initiative, + IsHidden = opp.IsHidden ? 1 : 0, + opp.Note + }); + + // configure column formats + exportOptions.AddColumnOptions("Identified Savings", "Currency2Dec"); + exportOptions.AddColumnOptions("Cases", "Accounting"); + exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\""); + + return ExportUtils.CreateExcelWorkbook(data, exportOptions); + } + + internal async Task> GetServiceLineNodes(Configuration.Configuration configuration, IEnumerable hierarchyPaths, CancellationToken cancellationToken) + { + var dimensionGuid = configuration.ServiceLineDimensionGuid; + var dimension = await _schemaServiceClient.GetDimensionByIdAsync(dimensionGuid) + ?? await _schemaServiceClient.GetDimensionByGlobalIdAsync("Service Line"); + return await GetNodesByHierarchyPath(dimension.DimensionGuid, hierarchyPaths, cancellationToken); + } + + internal async Task> GetNodesByGlobalId(string globalId, CancellationToken cancellationToken) + { + var dimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync(globalId); + return await GetNodesByHierarchyGuid(dimension.DefaultHierarchyGuid, cancellationToken); + } + + internal async Task> GetNodesByHierarchyGuid(Guid hierarchyGuid, CancellationToken cancellationToken) + { + return (await _schemaServiceClient.GetHierarchyNodesByHierarchyGuidAsync(hierarchyGuid, default, default, cancellationToken)).MapToFilterHelpers(); + } + + private async Task> GetNodesByHierarchyPath(Guid dimensionGuid, IEnumerable hierarchyPaths, CancellationToken cancellationToken) + { + var nodes = await _schemaServiceClient.GetHierarchyNodesFromHierarchyPathsAsync(dimensionGuid, hierarchyPaths, cancellationToken); + var filters = nodes.MapToFilterHelpers(); + return filters; + } + + // Info: Code of this method was copied from Jazz + private List UpdateGoal(JazzDbContext dbContext, Guid opportunityGuid, ENOpportunityRowDataChanges row, CancellationToken cancellationToken) + { + var levelID = row.LevelId; + var goalVolume = row.Volume; + var goalRevenuePerCase = row.RevenuePerCase; + var goalCostPerCase = row.CostPerCase; + + var baselineMetrics = dbContext.EncounterOpportunityMetricData.AsQueryable() + .Where(data => data.OpportunityGUID == opportunityGuid && data.LevelID == levelID && data.IsBaseline) + .ToList(); + + var baselineSum = (from metric in baselineMetrics + group metric by 1 into g + select new + { + Volume = g.Sum(x => x.Volume), + Cost = g.Sum(x => x.Cost), + Revenue = g.Sum(x => x.Revenue) + }).First(); + + if (baselineSum.Cost == 0 && baselineSum.Revenue == 0 && baselineSum.Volume == 0) + { + var weight = baselineMetrics.Count(); + baselineMetrics.ForEach(metric => + { + metric.GoalVolume = goalVolume.DivideBy(weight); + if (goalRevenuePerCase == 0 && metric.GoalRevenue != 0) + { + goalRevenuePerCase = (metric.GoalRevenue * weight).DivideBy(goalVolume); + } + if (goalCostPerCase == 0 && metric.GoalCost != 0) + { + goalCostPerCase = (metric.GoalCost * weight).DivideBy(goalVolume); + } + metric.GoalRevenuePerCase = goalRevenuePerCase; + metric.GoalCostPerCase = goalCostPerCase; + metric.IsBaseline = true; + }); + } + else + { + if (baselineMetrics.Any()) + { + baselineMetrics.ForEach(metric => + { + var monthlyWeightedVolume = (double)goalVolume * (metric.Volume.DivideBy(baselineSum.Volume)); + metric.GoalVolume = monthlyWeightedVolume; + metric.IsBaseline = true; + }); + + if (baselineMetrics.First().GoalRevenuePerCase != goalRevenuePerCase) + { + baselineMetrics.ForEach(metric => + { + metric.GoalRevenuePerCase = goalRevenuePerCase; + metric.IsBaseline = true; + }); + } + + if (baselineMetrics.First().GoalCostPerCase != goalCostPerCase) + { + baselineMetrics.ForEach(metric => + { + metric.GoalCostPerCase = goalCostPerCase; + metric.IsBaseline = true; + }); + } + } + } + + return baselineMetrics; + } + + private Dictionary> GetOpportnuityGridData(List metricData) + { + return metricData.GroupBy(m => m.LevelID) + .OrderBy(m => m.FirstOrDefault()?.Name ?? "System") + .Select(metricGroup => ENOpportunityRowData.KeyValuePair(metricGroup)) + .ToDictionary(key => key.Key, value => value.Value); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/Filters/ENFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/Filters/ENFilterOptions.cs new file mode 100644 index 0000000..3732da4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/Filters/ENFilterOptions.cs @@ -0,0 +1,10 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters +{ + public class ENFilterOptions + { + public IEnumerable OpportunityLevels { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/Filters/ENFilters.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/Filters/ENFilters.cs new file mode 100644 index 0000000..44023f3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/Filters/ENFilters.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters +{ + public partial class ENFilters : FiltersOpportunity + { + public int Cases { get; set; } + public IEnumerable OpportunityLevels { get; set; } + public EncounterGroup EncounterGroups { get; set; } + public SeasonalityType Seasonality { get; set; } + + public ENFilters() : base() + { + Cases = 0; + OpportunityLevels = new List(); + EncounterGroups = EncounterGroup.Both; + Seasonality = SeasonalityType.WithAndWithout; + } + + public override IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = base.GetFilters(context).ToList(); + + if (Cases > 0) + filters.Add(m => m.Cases >= Cases); + + if (OpportunityLevels.Any()) + filters.Add(m => OpportunityLevels.Contains(m.OpportunityLevel)); + + if (EncounterGroups != EncounterGroup.Both) + filters.Add(m => (int)EncounterGroups == (int)m.EncounterGroup); + + if (Seasonality != SeasonalityType.WithAndWithout) + filters.Add(m => (int)Seasonality == Convert.ToInt32(m.IsSeasonal)); + + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + var testSeasonality = Enum.TryParse(OpportunitySearch, true, out var seasonalityType) + && seasonalityType != SeasonalityType.WithAndWithout; + filters.Add(m => m.OpportunityKey.Contains(OpportunitySearch) + || m.Name.Contains(OpportunitySearch) + || m.EncounterGroupValue.Contains(OpportunitySearch) + || m.OpportunityLevel.Contains(OpportunitySearch) + || (testSeasonality && m.IsSeasonal == (seasonalityType == SeasonalityType.With))); + } + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/Filters/ENOpportunityBaseFilters.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/Filters/ENOpportunityBaseFilters.cs new file mode 100644 index 0000000..6b0911e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/Filters/ENOpportunityBaseFilters.cs @@ -0,0 +1,27 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters +{ + public class ENOpportunityBaseFilters + { + public int EncounterGroup { get; set; } + public bool IsSeasonal { get; set; } + public List CaseTypes { get; set; } + public List PatientPopulations { get; set; } + public string OpportunityLevel { get; set; } + + public OpportunityLevel EncounterOpportunityLevel { get; set; } + + public List Entities { get; set; } + public List ServiceLines { get; set; } + public List Specialties { get; set; } + public List Providers { get; set; } + + public bool IsBaseline { get; set; } = true; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/IENOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/IENOpportunityService.cs new file mode 100644 index 0000000..d1e2de8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/IENOpportunityService.cs @@ -0,0 +1,28 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public interface IENOpportunityService : IOpportunityService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + + Task ExportAsync(SortOptions sortOptions, ENFilters filters, CancellationToken cancellationToken); + + Task UpdateAsync(Guid databaseGuid, ENOpportunityDataModelWithFilters opportunity, CancellationToken cancellationToken); + Task CreateAsync(ENOpportunityDataModel opportunity, Guid databaseGuid, bool isTempoPage, CancellationToken cancellationToken); + Task GetENOpportunityById(Guid opportunityGuid, CancellationToken cancellationToken); + Task GetInitiativeInfoAsync(Guid opportunityGuid, CancellationToken cancellationToken); + Task UpdateGoalsAsync(Guid opportunityGuid, IEnumerable changes, CancellationToken cancellationToken); + Task SetOpportunityFilters(ENOpportunityDataModelWithFilters filters, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/OpportunityMetricCohortData.cs b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/OpportunityMetricCohortData.cs new file mode 100644 index 0000000..2d53f9a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Encounter/Opportunities/OpportunityMetricCohortData.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities +{ + [Keyless] + public class OpportunityMetricCohortData + { + public int LevelID { get; set; } + [NotMapped] + public string Name { get; set; } + public int Month { get; set; } + public int Volume { get; set; } + // TODO Revenue and Cost are defined in the database as float, consider changing to double? + public decimal Revenue { get; set; } + public decimal Cost { get; set; } + public decimal Margin => Revenue - Cost; + public decimal RevenuePerCase => Revenue.DivideBy(Volume); + public decimal CostPerCase => Cost.DivideBy(Volume); + public bool IsBaseline { get; set; } + + internal OpportunityMetricCohortData() + { + + } + internal ENOpportunityMetricData EncounterMetricData(bool isBaseline, Guid oppGuid, string opportunityLevel, + Dictionary defaultNames) + { + var name = !string.IsNullOrEmpty(Name) ? Name + : defaultNames.TryGetValue(LevelID, out var result) ? result : string.Empty; + var data = new ENOpportunityMetricData + { + EncounterOpportunityMetricDataGUID = Guid.NewGuid(), + OpportunityGUID = oppGuid, + Level = opportunityLevel, + LevelID = LevelID, + Name = name, + Month = Month, + Volume = Volume, + GoalVolume = Volume, + Revenue = (double)Revenue, + GoalRevenuePerCase = (double)RevenuePerCase, + Cost = (double)Cost, + GoalCostPerCase = (double)CostPerCase, + Margin = (double)Margin, + IsBaseline = isBaseline + }; + return data; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Benchmark/BenchmarkingInsightsGranularity.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Benchmark/BenchmarkingInsightsGranularity.cs new file mode 100644 index 0000000..e4c7f24 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Benchmark/BenchmarkingInsightsGranularity.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.Exploration.Benchmark +{ + public enum BenchmarkingInsightsGranularity + { + CaseTypeFamily = 0, + DrgCode = 1, + PrimaryCpt = 2 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Benchmark/HeatMapData.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Benchmark/HeatMapData.cs new file mode 100644 index 0000000..2c5bc1d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Benchmark/HeatMapData.cs @@ -0,0 +1,52 @@ +namespace Strata.ContinuousImprovement.Biz.Exploration.Benchmark +{ + /// + /// Simplified DTO for heatmap visualization containing only the essential + /// benchmarking metrics needed for the UI. Supports both case type family + /// and code-level (MS-DRG/APR-DRG/CPT) granularity. + /// + public class HeatMapData + { + /// + /// The display name for the row. Contains either the case type family name + /// or the code value (MS-DRG/APR-DRG/CPT) depending on the granularity level. + /// + public string Name { get; set; } + /// + /// The cost driver name (e.g., "Pharmacy", "Laboratory"). + /// + public string CostDriver { get; set; } + + /// + /// Customer's 50th percentile variable direct cost. + /// + public decimal? CustomerMedianCost { get; set; } + + /// + /// Benchmark cohort's 50th percentile variable direct cost. + /// + public decimal? BenchmarkMedianCost { get; set; } + + /// + /// Pre-calculated O/E ratio (CustomerMedianCost / BenchmarkMedianCost). + /// Null if benchmark is null or zero. + /// + public decimal? CostOeRatio { get; set; } + + /// + /// Customer's 50th percentile average length of stay. + /// + public decimal? CustomerMedianAlos { get; set; } + + /// + /// Benchmark cohort's 50th percentile average length of stay. + /// + public decimal? BenchmarkMedianAlos { get; set; } + + /// + /// Pre-calculated O/E ratio (CustomerMedianAlos / BenchmarkMedianAlos). + /// Null if benchmark is null or zero. + /// + public decimal? AlosOeRatio { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Benchmark/HeatMapDataMapper.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Benchmark/HeatMapDataMapper.cs new file mode 100644 index 0000000..45a003f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Benchmark/HeatMapDataMapper.cs @@ -0,0 +1,160 @@ +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.StrataSphereCompare.Client.Models; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Exploration.Benchmark +{ + /// + /// Maps CostDetailsDto to simplified HeatMapData for frontend visualization. + /// + public static class HeatMapDataMapper + { + /// + /// Converts a collection of CostDetailsDto to HeatMapData at the case type family level. + /// + /// The raw cost detail records from the benchmarking service. + /// True if using APR-DRG; false for MS-DRG classification. + /// Simplified heatmap data with pre-calculated O/E ratios. + public static IEnumerable ToHeatMapData( + IEnumerable costDetails, + bool isUsingAprdrg) + { + if (costDetails == null || !costDetails.Any()) + return Enumerable.Empty(); + + return costDetails + .Select(dto => MapToCaseTypeFamilyLevel(dto, isUsingAprdrg)) + .Where(data => data != null); + } + + /// + /// Converts a collection of CostDetailsDto to HeatMapData at the code level (MS-DRG/APR-DRG/CPT). + /// + /// The raw cost detail records from the benchmarking service. + /// True if using APR-DRG; false for MS-DRG classification. + /// Simplified heatmap data with pre-calculated O/E ratios at code granularity. + public static IEnumerable ToHeatMapDataCodeLevel( + IEnumerable costDetails, + bool isUsingAprdrg) + { + if (costDetails == null || !costDetails.Any()) + return Enumerable.Empty(); + + return costDetails + .Select(dto => MapToCodeLevel(dto, isUsingAprdrg)) + .Where(data => data != null); + } + + private static HeatMapData MapToCaseTypeFamilyLevel(CostDetailsDto dto, bool isUsingAprdrg) + { + var name = GetCaseTypeFamilyName(dto, isUsingAprdrg); + + if (string.IsNullOrEmpty(name) || name.Equals("Not Specified")) + return null; + + return new HeatMapData + { + Name = name, + CostDriver = dto.SphChargeCodeCostDriver, + CustomerMedianCost = dto.CustomerP50th_VariableDirectCost, + BenchmarkMedianCost = dto.P50th_VariableDirectCost, + CostOeRatio = MathExtensions.CalculateOeRatio(dto.CustomerP50th_VariableDirectCost, dto.P50th_VariableDirectCost), + CustomerMedianAlos = dto.CustomerP50th_Alos, + BenchmarkMedianAlos = dto.P50th_Alos, + AlosOeRatio = MathExtensions.CalculateOeRatio(dto.CustomerP50th_Alos, dto.P50th_Alos) + }; + } + + /// + /// Converts a collection of CostDetailsDto to HeatMapData at the primary CPT code level. + /// + /// The raw cost detail records from the benchmarking service. + /// Simplified heatmap data with pre-calculated O/E ratios at primary CPT code granularity. + public static IEnumerable ToHeatMapDataPrimaryCptLevel(IEnumerable costDetails) + { + if (costDetails == null || !costDetails.Any()) + return Enumerable.Empty(); + + return costDetails + .Select(dto => MapToPrimaryCptLevel(dto)) + .Where(data => data != null); + } + + private static HeatMapData MapToPrimaryCptLevel(CostDetailsDto dto) + { + var code = dto.SphPrimaryCptCode; + + if (string.IsNullOrEmpty(code)) + return null; + + return new HeatMapData + { + Name = code, + CostDriver = dto.SphChargeCodeCostDriver, + CustomerMedianCost = dto.CustomerP50th_VariableDirectCost, + BenchmarkMedianCost = dto.P50th_VariableDirectCost, + CostOeRatio = MathExtensions.CalculateOeRatio(dto.CustomerP50th_VariableDirectCost, dto.P50th_VariableDirectCost), + CustomerMedianAlos = dto.CustomerP50th_Alos, + BenchmarkMedianAlos = dto.P50th_Alos, + AlosOeRatio = MathExtensions.CalculateOeRatio(dto.CustomerP50th_Alos, dto.P50th_Alos) + }; + } + + private static HeatMapData MapToCodeLevel(CostDetailsDto dto, bool isUsingAprdrg) + { + var code = GetCode(dto, isUsingAprdrg); + + if (string.IsNullOrEmpty(code)) + return null; + + return new HeatMapData + { + Name = code, + CostDriver = dto.SphChargeCodeCostDriver, + CustomerMedianCost = dto.CustomerP50th_VariableDirectCost, + BenchmarkMedianCost = dto.P50th_VariableDirectCost, + CostOeRatio = MathExtensions.CalculateOeRatio(dto.CustomerP50th_VariableDirectCost, dto.P50th_VariableDirectCost), + CustomerMedianAlos = dto.CustomerP50th_Alos, + BenchmarkMedianAlos = dto.P50th_Alos, + AlosOeRatio = MathExtensions.CalculateOeRatio(dto.CustomerP50th_Alos, dto.P50th_Alos) + }; + } + + /// + /// Gets the appropriate case type family name based on the DRG type configuration. + /// Priority: DRG-specific (APRDRG or MSDRG) > CPT + /// + private static string GetCaseTypeFamilyName(CostDetailsDto dto, bool isUsingAprdrg) + { + // First try DRG-specific name + var drgName = isUsingAprdrg + ? dto.AprDrgCaseTypeFamilyName + : dto.MsDrgCaseTypeFamilyName; + + if (!string.IsNullOrEmpty(drgName)) + return drgName; + + // Fall back to CPT if DRG name is not available + return dto.SphCptCodeCaseTypeFamilyName; + } + + /// + /// Gets the appropriate code value based on the DRG type configuration. + /// Priority: DRG-specific (APRDRG or MSDRG) > CPT + /// + private static string GetCode(CostDetailsDto dto, bool isUsingAprdrg) + { + // First try DRG-specific code + var drgCode = isUsingAprdrg + ? dto.SphAprDrgCode + : dto.SphMsDrgCode; + + if (!string.IsNullOrEmpty(drgCode)) + return drgCode; + + // Fall back to CPT if DRG code is not available + return dto.SphPrimaryCptCode; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CTFEncounterDetail.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CTFEncounterDetail.cs new file mode 100644 index 0000000..79f860e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CTFEncounterDetail.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CTFEncounterDetail : ICTFEncounterDetail + { + public int EncounterId { get; set; } + public string EncounterRecordNumber { get; set; } + + public int CTFType { get; set; } + + public int EntityId { get; set; } + public string Code { get; set; } + public int PhysicianId { get; set; } + + public float LengthOfStay { get; set; } + public double VariableDirectCost { get; set; } + public double IdentifiedVariation { get; set; } + public double Charges { get; set; } + + public short IsExpired { get; set; } + public bool IsExcluded { get; set; } + + public string DischargeStatusCode { get; set; } + public string DischargeStatusDescription { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CTFGroupCalcs.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CTFGroupCalcs.cs new file mode 100644 index 0000000..0ae185d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CTFGroupCalcs.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CTFGroupCalcs + { + public int CTFId { get; set; } + public string CTFName { get; set; } + public double MedianVDC { get; set; } + + } + + public class EntityGroupCalcs : ICTFGroupCalcs + { + public int EntityId { get; set; } + public string EntityName { get; set; } + public double MedianVDC { get; set; } + public double MedianLOS { get; set; } + public double AverageLOS { get; set; } + public double MedianCharges { get; set; } + public string Id { get { return this.EntityId.ToString(); } } + public string Name { get { return this.EntityName; } } + } + + public class EntityCodeGroupCalcs : ICTFGroupCalcs + { + public int EntityId { get; set; } + public string Code { get; set; } + public string CodeName { get; set; } + public double MedianVDC { get; set; } + public double MedianLOS { get; set; } + public double AverageLOS { get; set; } + public double MedianCharges { get; set; } + public string Id { get { return this.Code; } } + public string Name { get { return this.CodeName; } } + } + + public class EntityCodePhysicianGroupCalcs : ICTFGroupCalcs + { + public int EntityId { get; set; } + public string Code { get; set; } + public int PhysicianId { get; set; } + public string PhysicianName { get; set; } + public double MedianVDC { get; set; } + public double MedianLOS { get; set; } + public double AverageLOS { get; set; } + public double MedianCharges { get; set; } + public string Id { get { return this.PhysicianId.ToString(); } } + public string Name { get { return this.PhysicianName; } } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeDetailRowData.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeDetailRowData.cs new file mode 100644 index 0000000..43500b1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeDetailRowData.cs @@ -0,0 +1,111 @@ +using Strata.ContinuousImprovement.Biz.Utilities; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CaseTypeDetailRowData + { + + internal CaseTypeDetailRowData(IEnumerable group, ICTFGroupCalcs groupCalcs) + { + Id = groupCalcs.Id; + Name = groupCalcs.Name; + + TotalEncounters = group.Count(); + TotalVariationEncounters = group.Count(x => x.IdentifiedVariation > 0); + IdentifiedVariation = group.Where(x => x.IdentifiedVariation > 0).Sum(e => e.IdentifiedVariation); + IncludedEncounterCount = group.Count(x => !x.IsExcluded); + MortalityRate = group.Count(x => !x.IsExcluded && x.IsExpired == 1).DivideBy(group.Count()); + IncludedEncountersBelowMedian = group.Count(x => !x.IsExcluded && x.IdentifiedVariation <= 0); + IncludedEncountersAboveMedian = group.Count(x => !x.IsExcluded && x.IdentifiedVariation > 0); + IncludedVariation = group.Where(x => !x.IsExcluded && x.IdentifiedVariation > 0).Sum(x => x.IdentifiedVariation); + ExcludedEncountersAboveMedian = group.Count(x => x.IsExcluded && x.IdentifiedVariation > 0); + ExcludedVariation = group.Where(x => x.IsExcluded && x.IdentifiedVariation > 0).Select(x => x.IdentifiedVariation).DefaultIfEmpty().Sum(); + + MedianLos = groupCalcs.MedianLOS; + AverageLos = groupCalcs.AverageLOS; + MedianGrossCharges = groupCalcs.MedianCharges; + MedianCost = groupCalcs.MedianVDC; + } + + internal CaseTypeDetailRowData(ICTFEncounterDetail encounter) + { + Id = encounter.EncounterId.ToString(); + Name = encounter.EncounterRecordNumber; + TotalEncounters = 1; + IsExcluded = encounter.IsExcluded; + + if (encounter.IdentifiedVariation > 0) + { + TotalVariationEncounters = 1; + IdentifiedVariation = encounter.IdentifiedVariation; + if (IsExcluded) + { + ExcludedEncountersAboveMedian = 1; + ExcludedVariation = encounter.IdentifiedVariation; + } + else + { + IncludedEncounterCount = 1; + IncludedEncountersAboveMedian = 1; + IncludedVariation = encounter.IdentifiedVariation; + } + } + else // encounter.IdentifiedVariation <= 0 + { + if (!IsExcluded) + { + IncludedEncounterCount = 1; + IncludedEncountersBelowMedian = 1; + } + } + + MortalityRate = encounter.IsExpired; + DischargeStatusCode = encounter.DischargeStatusCode; + DischargeStatusDescription = encounter.DischargeStatusDescription; + MedianLos = encounter.LengthOfStay; + AverageLos = encounter.LengthOfStay; // For single encounter, average = actual value + MedianCost = encounter.VariableDirectCost; + MedianGrossCharges = encounter.Charges; + } + + public string Id { get; set; } + public string Name { get; set; } + public int TotalEncounters { get; set; } + public int TotalVariationEncounters { get; set; } + public double IdentifiedVariation { get; set; } + public int IncludedEncounterCount { get; set; } + public double MortalityRate { get; set; } + public double MedianLos { get; set; } + public double AverageLos { get; set; } + public double MedianCost { get; set; } + public int IncludedEncountersBelowMedian { get; set; } + public int IncludedEncountersAboveMedian { get; set; } + public double IncludedVariation { get; set; } + public int ExcludedEncountersAboveMedian { get; set; } + public double ExcludedVariation { get; set; } + public double MedianGrossCharges { get; set; } + public bool IsExcluded { get; set; } + public string DischargeStatusCode { get; set; } + public string DischargeStatusDescription { get; set; } + public double PeerMedianALos { get; set; } + public double PeerMedianCost { get; set; } + + /// + /// Median adjusted length of stay based on the customer-defined benchmark cohort, + /// as opposed to , which is calculated from the external peer group. + /// + public double CustomerBenchmarkMedianALos { get; set; } + /// + /// Median cost based on the customer-defined benchmark cohort, + /// as opposed to , which is calculated from the external peer group. + /// + public double CustomerBenchmarkMedianCost { get; set; } + + public double? CostOeRatio => MathExtensions.CalculateOeRatio(CustomerBenchmarkMedianCost, PeerMedianCost); + public double? AlosOeRatio => MathExtensions.CalculateOeRatio(CustomerBenchmarkMedianALos, PeerMedianALos); + + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamily.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamily.cs new file mode 100644 index 0000000..c672ba7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamily.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + [Table("CaseTypeFamily", Schema = "dss")] + public class CaseTypeFamily + { + public int CaseTypeFamilyId { get; set; } + public string Name { get; set; } + public string CaseTypeFamilyGlobalId { get; set; } + + public static string CaseTypeFamilyTypeDefault = "CPT"; + [NotMapped] + public string CaseTypeFamilyType => + string.IsNullOrEmpty(CaseTypeFamilyGlobalId) + ? CaseTypeFamilyTypeDefault + : CaseTypeFamilyGlobalId.Substring(0, 1) switch + { + "A" => "APRDRG", + "M" => "MSDRG", + "O" => "CPT", + _ => CaseTypeFamilyTypeDefault + }; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyDetailBase.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyDetailBase.cs new file mode 100644 index 0000000..1e62db0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyDetailBase.cs @@ -0,0 +1,31 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CaseTypeFamilyDetailBase : ICaseTypeFamilyDetailBase + { + public int EncounterId { get; set; } + public DateTime DischargeDateTime { get; set; } + public string EncounterRecordNumber { get; set; } + public int EntityId { get; set; } + public double IdentifiedVariation { get; set; } + public short IsExpired { get; set; } + public float LengthOfStay { get; set; } + public float MedianLengthOfStay { get; set; } + public float AverageLengthOfStay { get; set; } + public double MedianVariableDirectCost { get; set; } + public double MedianGrossCharges { get; set; } + public double VariableDirectCost { get; set; } + public double Charges { get; set; } + public bool IsExcluded { get; set; } + public float IqrLos { get; set; } + public float IqrVdc { get; set; } + public float MadLos { get; set; } + public float MadVdc { get; set; } + public string DischargeStatusCode { get; set; } + public string DischargeStatusDescription { get; set; } + public double MortalityRate { get; set; } + public string ROM { get; set; } + public string SOI { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyDetailData.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyDetailData.cs new file mode 100644 index 0000000..aa38b7e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyDetailData.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CaseTypeFamilyDetailData + { + public string Key { get; set; } + public CaseTypeDetailRowData Data { get; set; } + public IEnumerable Children { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyDetailInfo.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyDetailInfo.cs new file mode 100644 index 0000000..cfbdea7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyDetailInfo.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CaseTypeFamilyDetailInfo + { + public string CaseTypeFamilyType { get; set; } = CaseTypeFamily.CaseTypeFamilyTypeDefault; + public string CaseTypeFamilyName { get; set; } + public CaseTypeFamilyMetric Metrics { get; set; } + public IEnumerable CaseTypeFamilyDetails { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyEncounterInfo.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyEncounterInfo.cs new file mode 100644 index 0000000..04ce9e7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyEncounterInfo.cs @@ -0,0 +1,15 @@ +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + //This class represents the return type of our snowflake query + //It contains encounter level information as well as some grouped info such as medians and identified variation + public class CaseTypeFamilyEncounterInfo : CaseTypeFamilyDetailBase + { + public int CaseTypeFamilyId { get; set; } + public string CaseTypeFamilyName { get; set; } + public double Vdc { get; set; } + public double MedianVdc { get; set; } + public double SeventyFivePercentileVdc { get; set; } + public double SeventyFivePercentileLos { get; set; } + public string CostDriver { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyGroupCalcs.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyGroupCalcs.cs new file mode 100644 index 0000000..fe8fcb4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyGroupCalcs.cs @@ -0,0 +1,36 @@ +using MoreLinq; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CaseTypeFamilyGroupCalcs + { + public CTFGroupCalcs CtfGroupCalcs { get; set; } + public IEnumerable EntityGroupCalcs { get; set; } + public IEnumerable EntityCodeGroupCalcs { get; set; } + public IEnumerable EntityCodePhysicianGroupCalcs { get; set; } + public IEnumerable CtfEncounterDetails { get; set; } + + public CaseTypeFamilyGroupCalcs() + { + CtfGroupCalcs = new CTFGroupCalcs(); + EntityGroupCalcs = Enumerable.Empty(); + EntityCodeGroupCalcs = Enumerable.Empty(); + EntityCodePhysicianGroupCalcs = Enumerable.Empty(); + CtfEncounterDetails = Enumerable.Empty(); + } + + public CaseTypeFamilyGroupCalcs(ISqlGridReaderWrapper results) + : this() + { + CtfGroupCalcs = results.Read().FirstOrDefault() ?? new CTFGroupCalcs(); + EntityGroupCalcs = [.. results.Read()]; + EntityCodeGroupCalcs = [.. results.Read()]; + EntityCodePhysicianGroupCalcs = [.. results.Read()]; + CtfEncounterDetails = results.Read(); + CtfEncounterDetails.ForEach(x => { x.IdentifiedVariation = x.VariableDirectCost - CtfGroupCalcs.MedianVDC; }); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyMetric.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyMetric.cs new file mode 100644 index 0000000..319102c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyMetric.cs @@ -0,0 +1,12 @@ +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CaseTypeFamilyMetric + { + public int TotalEncounters { get; set; } + public double IdentifiedVariation { get; set; } + public int EncountersIncluded { get; set; } + public double VariationIncluded { get; set; } + public int EncountersExcluded { get; set; } + public double VariationExcluded { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilySummaryData.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilySummaryData.cs new file mode 100644 index 0000000..3534c61 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilySummaryData.cs @@ -0,0 +1,62 @@ +using MoreLinq; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Utilities; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CaseTypeFamilySummaryData + { + public string Key { get; set; } + public string Name { get; set; } + public bool Leaf { get; set; } + public EncounterGroupSummary Data { get; set; } + public IEnumerable Children { get; set; } + + public CaseTypeFamilySummaryData() { } + + //CTF summary + public CaseTypeFamilySummaryData(CaseTypeFamilySummaryInfo ctf) + { + Key = $"1-{ctf.CaseTypeFamilyId}"; + Name = ctf.CaseTypeFamilyName; + Data = new EncounterGroupSummary(ctf); + Data.Key = Key; + } + + //Children - CTF x Cost driver + public CaseTypeFamilySummaryData(string key, IEnumerable group, ExclusionCriteria exclusionCriteria, + int level = 1, bool withChildren = false) + { + Key = $"{level}-{key}"; + var item = group.FirstOrDefault(); + if (item == null) { Name = "".IsNotSpecified(); } + else + { + Name = level == 1 + ? item.CaseTypeFamilyName.IsNotSpecified() + : item.CostDriver.IsNotSpecified(); + } + Leaf = level != 1; + Data = new EncounterGroupSummary(group, exclusionCriteria, Name, level > 1); + Data.Key = Key; + if (level > 1) { return; } + var children = new List(); + if (level == 1 && !withChildren) + { + children.Add(new CaseTypeFamilySummaryData("", [], exclusionCriteria, 2)); + Children = children; + } + if (level > 1 || !withChildren) { return; } + var batches = group.GroupBy(x => x.CostDriver, (CostDriver, Items) => new { CostDriver, Items }).Batch(10); + foreach (var batch in batches) + { + batch.ForEach(costDriverGroup => + children.Add(new CaseTypeFamilySummaryData(costDriverGroup.CostDriver, costDriverGroup.Items, exclusionCriteria, 2))); + + } + Children = children.OrderBy(caseTypeFamilySummary => caseTypeFamilySummary.Name); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyTrimInfo.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyTrimInfo.cs new file mode 100644 index 0000000..de55e0b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeFamilyTrimInfo.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public class CaseTypeFamilyTrimInfo + { + public int CaseTypeFamilyId { get; set; } + public float MadVdc { get; set; } + public float MadLos { get; set; } + public double MedianVdc { get; set; } + public float MedianLos { get; set; } + public double SeventyFivePercentileVdc { get; set; } + public double SeventyFivePercentileLos { get; set; } + public float IqrVdc { get; set; } + public float IqrLos { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeSummaryInfo.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeSummaryInfo.cs new file mode 100644 index 0000000..020dc1a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/CaseTypeSummaryInfo.cs @@ -0,0 +1,24 @@ +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + //This class represents the return type of our snowflake query + public class CaseTypeFamilySummaryInfo + { + public int CaseTypeFamilyId { get; set; } + public string CaseTypeFamilyName { get; set; } + public double Vdc { get; set; } + public double MedianVdc { get; set; } + public int TotalEncounters { get; set; } + public int TotalVariationEncounters { get; set; } + public double IdentifiedVariation { get; set; } + public int IncludedEncounterCount { get; set; } + public double MortalityRate { get; set; } + public double MedianLos { get; set; } + public double AverageLengthOfStay { get; set; } + public double MedianCost { get; set; } + public int IncludedEncountersBelowMedian { get; set; } + public int IncludedEncountersAboveMedian { get; set; } + public double IncludedVariation { get; set; } + public int ExcludedEncountersAboveMedian { get; set; } + public double ExcludedVariation { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/ICTFEncounterDetail.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/ICTFEncounterDetail.cs new file mode 100644 index 0000000..8754823 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/ICTFEncounterDetail.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public interface ICTFEncounterDetail + { + int EncounterId { get; set; } + string EncounterRecordNumber { get; set; } + + int CTFType { get; set; } + + int EntityId { get; set; } + string Code { get; set; } + int PhysicianId { get; set; } + + float LengthOfStay { get; set; } + double VariableDirectCost { get; set; } + double IdentifiedVariation { get; set; } + double Charges { get; set; } + + short IsExpired { get; set; } + bool IsExcluded { get; set; } + + string DischargeStatusCode { get; set; } + string DischargeStatusDescription { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/ICTFGroupCalcs.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/ICTFGroupCalcs.cs new file mode 100644 index 0000000..8ce8b17 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/ICTFGroupCalcs.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public interface ICTFGroupCalcs + { + string Id { get; } + string Name { get; } + double MedianVDC { get; set; } + double MedianLOS { get; set; } + double AverageLOS { get; set; } + double MedianCharges { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/ICaseTypeFamilyDetailBase.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/ICaseTypeFamilyDetailBase.cs new file mode 100644 index 0000000..38d1614 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/CaseTypeFamilies/ICaseTypeFamilyDetailBase.cs @@ -0,0 +1,27 @@ +namespace Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies +{ + public interface ICaseTypeFamilyDetailBase + { + int EncounterId { get; set; } + string EncounterRecordNumber { get; set; } + int EntityId { get; set; } + double IdentifiedVariation { get; set; } + double Charges { get; set; } + float IqrLos { get; set; } + float IqrVdc { get; set; } + bool IsExcluded { get; set; } + short IsExpired { get; set; } + float LengthOfStay { get; set; } + float MadLos { get; set; } + float MadVdc { get; set; } + float MedianLengthOfStay { get; set; } + double MedianVariableDirectCost { get; set; } + double MedianGrossCharges { get; set; } + double VariableDirectCost { get; set; } + string DischargeStatusCode { get; set; } + string DischargeStatusDescription { get; set; } + double MortalityRate { get; set; } + string ROM { get; set; } + string SOI { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/EncounterGroupSummary.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/EncounterGroupSummary.cs new file mode 100644 index 0000000..27d026d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/EncounterGroupSummary.cs @@ -0,0 +1,128 @@ +using Microsoft.IdentityModel.Tokens; +using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Utilities; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public class EncounterGroupSummary + { + public string Key { get; set; } + public int Id { get; set; } + public string Name { get; set; } + public int TotalEncounters { get; set; } + public int TotalVariationEncounters { get; set; } + public double IdentifiedVariation { get; set; } + public int IncludedEncounterCount { get; set; } + public double MortalityRate { get; set; } + public double MedianLos { get; set; } + public double AverageLengthOfStay { get; set; } + public double MedianCost { get; set; } + public int IncludedEncountersBelowMedian { get; set; } + public int IncludedEncountersAboveMedian { get; set; } + public double IncludedVariation { get; set; } + public int ExcludedEncountersAboveMedian { get; set; } + public double ExcludedVariation { get; set; } + public double MadLos { get; set; } + public double MadCost { get; set; } + public double IqrLos { get; set; } + public double IqrCost { get; set; } + public double PeerMedianALos { get; set; } + public double PeerMedianCost { get; set; } + public double CustomerBenchmarkMedianCost { get; set; } + public double CustomerBenchmarkALos { get; set; } + public double? CostOeRatio => MathExtensions.CalculateOeRatio(CustomerBenchmarkMedianCost, PeerMedianCost); + public double? AlosOeRatio => MathExtensions.CalculateOeRatio(CustomerBenchmarkALos, PeerMedianALos); + + + public EncounterGroupSummary() + { + + } + + public EncounterGroupSummary(CaseTypeFamilySummaryInfo caseTypeSummary) + { + Id = caseTypeSummary.CaseTypeFamilyId; + Name = caseTypeSummary.CaseTypeFamilyName; + TotalEncounters = caseTypeSummary.TotalEncounters; + TotalVariationEncounters = caseTypeSummary.TotalVariationEncounters; + IdentifiedVariation = caseTypeSummary.IdentifiedVariation; + + IncludedEncounterCount = caseTypeSummary.IncludedEncounterCount; + MortalityRate = caseTypeSummary.MortalityRate; + MedianLos = caseTypeSummary.MedianLos; + AverageLengthOfStay = caseTypeSummary.AverageLengthOfStay; + MedianCost = caseTypeSummary.MedianCost; + + IncludedEncountersBelowMedian = caseTypeSummary.IncludedEncountersBelowMedian; + IncludedEncountersAboveMedian = caseTypeSummary.IncludedEncountersAboveMedian; + IncludedVariation = caseTypeSummary.IncludedVariation; + + ExcludedEncountersAboveMedian = caseTypeSummary.ExcludedEncountersAboveMedian; + ExcludedVariation = caseTypeSummary.ExcludedVariation; + } + + public EncounterGroupSummary(IEnumerable encounterGroupInfos, ExclusionCriteria exclusionCriteria, string groupName, bool isDetailRow = false) + { + if (encounterGroupInfos.IsNullOrEmpty()) { return; } + var includedEncounterInfos = exclusionCriteria.LosExclusionType switch + { + ExclusionCriteriaType.Mad => encounterGroupInfos + .Where(encounterInfo => encounterInfo.LengthOfStay <= (encounterInfo.MadLos * exclusionCriteria.LosExclusionValue) + encounterInfo.MedianLengthOfStay), + ExclusionCriteriaType.Iqr => encounterGroupInfos + .Where(encounterInfo => encounterInfo.LengthOfStay <= (encounterInfo.IqrLos * exclusionCriteria.LosExclusionValue) + encounterInfo.SeventyFivePercentileLos), + _ => encounterGroupInfos + }; + + includedEncounterInfos = exclusionCriteria.CostExclusionType switch + { + ExclusionCriteriaType.Mad => includedEncounterInfos + .Where(encounterInfo => encounterInfo.Vdc <= (encounterInfo.MadVdc * exclusionCriteria.CostExclusionValue) + encounterInfo.MedianVdc), + ExclusionCriteriaType.Iqr => includedEncounterInfos + .Where(encounterInfo => encounterInfo.Vdc <= (encounterInfo.IqrVdc * exclusionCriteria.CostExclusionValue) + encounterInfo.SeventyFivePercentileVdc), + _ => includedEncounterInfos + }; + + var excludedEncounterInfos = encounterGroupInfos.Except(includedEncounterInfos); + + Id = encounterGroupInfos.First().CaseTypeFamilyId; + Name = groupName; + + var encounterInfosAbove = encounterGroupInfos.Where(i => i.IdentifiedVariation > 0); + + TotalEncounters = encounterGroupInfos.DistinctBy(x => x.EncounterId).Count(); + TotalVariationEncounters = encounterInfosAbove.DistinctBy(x => x.EncounterId).Count(); + IdentifiedVariation = encounterInfosAbove.Sum(e => e.IdentifiedVariation); + + var distinctIncludedEncounters = includedEncounterInfos.DistinctBy(x => x.EncounterId); + IncludedEncounterCount = distinctIncludedEncounters.Count(); + MortalityRate = distinctIncludedEncounters.Any() ? distinctIncludedEncounters.Average(i => i.IsExpired) : 0; + MedianLos = distinctIncludedEncounters.Median(i => i.LengthOfStay); + AverageLengthOfStay = distinctIncludedEncounters.Any() ? distinctIncludedEncounters.Average(i => i.LengthOfStay) : 0; + MedianCost = CalculateMedianCost(isDetailRow, includedEncounterInfos); + + var includedInfosAbove = includedEncounterInfos.Where(i => i.IdentifiedVariation > 0); + IncludedEncountersAboveMedian = includedInfosAbove.DistinctBy(x => x.EncounterId).Count(); + IncludedEncountersBelowMedian = IncludedEncounterCount - IncludedEncountersAboveMedian; + IncludedVariation = includedEncounterInfos.Any() ? includedInfosAbove.Sum(e => e.IdentifiedVariation) : 0; + + var excludedInfosAbove = excludedEncounterInfos.Where(i => i.IdentifiedVariation > 0); + ExcludedEncountersAboveMedian = excludedEncounterInfos.DistinctBy(x => x.EncounterId).Count(); + ExcludedVariation = excludedEncounterInfos.Any() ? excludedInfosAbove.Sum(e => e.IdentifiedVariation) : 0; + } + + private static double CalculateMedianCost(bool isDetailRow, IEnumerable includedEncounterInfos) + { + if (isDetailRow) + { + return includedEncounterInfos.Select(i => i.MedianVariableDirectCost).FirstOrDefault(); + } + + // CTF Level - Total VDC Median for encounters within CTF + var costDriverEncounters = includedEncounterInfos.GroupBy(x => x.EncounterId).Select(group => new { TotalVDC = group.Sum(encounters => encounters.VariableDirectCost) }); + return costDriverEncounters.Select(x => x.TotalVDC).Median(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/EncounterPhysicianInfo.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/EncounterPhysicianInfo.cs new file mode 100644 index 0000000..84bcf4f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/EncounterPhysicianInfo.cs @@ -0,0 +1,96 @@ +using DocumentFormat.OpenXml.Wordprocessing; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Pipelines.Sockets.Unofficial.SocketConnection; +using Jazz = Strata.DecisionSupport.Models.Jazz; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public class PhysicianInfo + { + public int PhysicianID { get; set; } + public string PhysicianName { get; set; } + public Guid PhysicianGUID { get; set; } + public string StrPhysicianGUID + { + set + { + this.PhysicianGUID = Guid.TryParse(value, out var valueGuid) ? valueGuid : Guid.Empty; + } + } + public int PhysicianGroupAffiliationID { get; set; } + public string PhysicianGroupAffiliation { get; set; } + public Guid PhysicianGroupAffiliationGUID { get; set; } + public string StrPhysicianGroupAffiliationGUID + { + set + { + this.PhysicianGroupAffiliationGUID = Guid.TryParse(value, out var valueGuid) ? valueGuid : Guid.Empty; + } + } + public int PhysicianSpecialtyID { get; set; } + public string PhysicianSpecialty { get; set; } + public Guid PhysicianSpecialtyGUID { get; set; } + public string StrPhysicianSpecialtyGUID + { + set + { + this.PhysicianSpecialtyGUID = Guid.TryParse(value, out var valueGuid) ? valueGuid : Guid.Empty; + } + } + } + + public class EncounterInfoBase + { + public long EncounterId { get; set; } + public string EncounterRecordNumber { get; set; } + public int PhysicianID { get; set; } + public int AgeCohortID { get; set; } + public int EntityID { get; set; } + public int ServiceLineID { get; set; } + public double LengthOfStay { get; set; } + public bool IsQVIEncounter { get; set; } + public DateTime DischargeDateTime { get; set; } + public bool IsExcluded { get; set; } + } + + public class EncounterPhysicianInfo : EncounterInfoBase + { + public string PhysicianName { get; set; } + public Guid PhysicianGUID { get; set; } + public int PhysicianGroupAffiliationID { get; set; } + public string PhysicianGroupAffiliation { get; set; } + public Guid PhysicianGroupAffiliationGUID { get; set; } + public int PhysicianSpecialtyID { get; set; } + public string PhysicianSpecialty { get; set; } + public Guid PhysicianSpecialtyGUID { get; set; } + + public void AddPhysicianData(PhysicianInfo physician) + { + PhysicianName = physician.PhysicianName; + PhysicianGUID = physician.PhysicianGUID; + PhysicianGroupAffiliationID = physician.PhysicianGroupAffiliationID; + PhysicianGroupAffiliation = physician.PhysicianGroupAffiliation; + PhysicianGroupAffiliation = physician.PhysicianGroupAffiliation; + PhysicianGroupAffiliationGUID = physician.PhysicianGroupAffiliationGUID; + PhysicianSpecialtyID = physician.PhysicianSpecialtyID; + PhysicianSpecialty = physician.PhysicianSpecialty; + PhysicianSpecialtyGUID = physician.PhysicianSpecialtyGUID; + } + } + + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true, + PropertyNameMappingStrategy = PropertyNameMappingStrategy.CaseInsensitive)] + public static partial class EncounterInfoMapper + { + public static partial EncounterPhysicianInfo MapToEncounterPhysicianInfo(this EncounterInfoBase src); + public static partial Jazz.ExplorationEncounterInfo MapToExplorationEncounterInfo(this EncounterPhysicianInfo src); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationFilterService.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationFilterService.cs new file mode 100644 index 0000000..2b3a3d3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationFilterService.cs @@ -0,0 +1,459 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.PatientType; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Schema.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using Strata.StrataSphereCompare.Client.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using static Strata.ContinuousImprovement.Biz.Exploration.ExplorationService; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public class ExplorationFilterService : IExplorationFilterService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ISchemaServiceClient _schemaServiceClient; + private readonly ILogger _logger; + + /// + /// Valid SPH cost drivers to avoid looking at any custom/variation across customer base + /// + private static class SphCostDrivers + { + public const string AnesthesiaAndRecovery = "Anesthesia and Recovery"; + public const string Imaging = "Imaging"; + public const string Laboratory = "Laboratory"; + public const string LengthOfStay = "LOS"; + public const string OrTime = "OR Time"; + public const string Pharmacy = "Pharmacy"; + public const string Supplies = "Supplies"; + } + + private static readonly string[] ValidSphCostDrivers = + { + SphCostDrivers.AnesthesiaAndRecovery, + SphCostDrivers.Imaging, + SphCostDrivers.Laboratory, + SphCostDrivers.LengthOfStay, + SphCostDrivers.OrTime, + SphCostDrivers.Pharmacy, + SphCostDrivers.Supplies + }; + + public ExplorationFilterService( + IAsyncDbContextFactory dbContextFactory, + ISchemaServiceClient schemaServiceClient, + ILogger logger) + { + _dbContextFactory = dbContextFactory; + _schemaServiceClient = schemaServiceClient; + _logger = logger; + } + + public async Task<(string QueryFilters, string EncounterSummaryFilters)> GetFilterStringsAsync(IEnumerable filterChipItems, IQueryParamBase queryParams, CancellationToken cancellationToken) + { + var filterCollection = await GetFiltersAsync(filterChipItems, queryParams, cancellationToken); + var queryFilters = filterCollection.QueryFilters.Any() ? "WHERE " + string.Join("\r\n AND ", filterCollection.QueryFilters) + "\r\n" : "\r\n"; + var encounterSummaryFilters = filterCollection.EncounterSummaryFilters.Any() ? "AND " + string.Join("\r\n AND ", filterCollection.EncounterSummaryFilters) + "\r\n" : "\r\n"; + return (queryFilters, encounterSummaryFilters); + } + + public async Task GetFiltersAsync(IEnumerable filterChipItems, IQueryParamBase queryParams, CancellationToken cancellationToken) + { + if (filterChipItems.IsEmpty()) { return new FilterCollection(); } + var ids = new List(); + var queryFilters = new List(); + var encounterSummaryFilters = new List(); + var filterCollection = new FilterCollection(); + var filterChipGroups = filterChipItems + .OrderBy(fci => fci.ChipType).ThenBy(fci => fci.Key); + + foreach (var filterChipItem in filterChipGroups) + { + // UI Filter chip only, doesn't produce any expected microservice query constructs + if (new[] { ChipKey.LengthOfStay, ChipKey.VariableDirectCost }.Contains(filterChipItem.Key)) + continue; + + var isIn = filterChipItem.Operator == OperatorType.In ? "In" : "Not In"; + try + { + switch (filterChipItem.ChipType) + { + case ChipType.ScoreMember when filterChipItem.Filters.Count > 0: + await ProcessScoreMemberFilter(filterChipItem, isIn, queryFilters, encounterSummaryFilters, cancellationToken); + break; + case ChipType.Boolean when filterChipItem.IsSelected.HasValue: + ProcessBooleanFilter(filterChipItem, isIn, queryFilters); + break; + case ChipType.Special when filterChipItem.Filters.Count > 0: + ProcessSpecialFilter(filterChipItem, isIn, queryFilters); + break; + case ChipType.CostDriver when filterChipItem.Filters.Count > 0: + ProcessCostDriverFilter(filterChipItem, queryFilters); + break; + case ChipType.DateRange when filterChipItem.DateRange.Count() == 2: + ProcessDateRangeFilter(filterChipItem, queryParams); + break; + } + } + catch (Exception ex) + { + _logger.LogServiceError(ex, info: new Dictionary { { "FilterBars", filterChipItem } }); + throw; + } + } + + filterCollection.QueryFilters = queryFilters.Where(qf => !string.IsNullOrEmpty(qf)); + filterCollection.EncounterSummaryFilters = encounterSummaryFilters.Where(esf => !string.IsNullOrEmpty(esf)); + return filterCollection; + } + + /// + /// Builds a instance based on the supplied filter chips + /// and drill-down context for the selected case type family and cost driver. + /// + /// The case type family being drilled into for cost details. + /// The identifier of the cost driver for which drill-down filters are being built. + /// The collection of filter chip items used to construct population and benchmarking filters. + /// Indicates whether APR-DRG logic should be applied when building population criteria filters. + /// If true, forces inclusion of the cost driver column in the resulting filters. + /// If true, includes the code column in the resulting filters for the drill-down. + /// Token used to observe cancellation requests for the asynchronous operation. + /// A populated representing the filters to apply to the cost details query. + public async Task BuildDataFiltersAsync(CaseTypeFamily drillDownCaseTypeFamily, string drillDownCostDriver, Filters.Filter filters, bool isUsingAprdrg, bool forceIncludeOfCostDriverColumn, bool includeCodeColumn, CancellationToken cancellationToken) + { + var dataFilters = new CostDetailsFilterDto(); + var filterChipItems = filters.FilterChipItems; + + if (filterChipItems == null || filterChipItems.Count == 0) + return dataFilters; + + // Apply filter for supported "in" type only, OperatorType.In + filterChipItems = filterChipItems.Where(x => x.Operator == OperatorType.In).ToList(); + + await ApplyPopulationCriteriaFiltersAsync(isUsingAprdrg, dataFilters, filterChipItems, cancellationToken); + ApplyBenchmarkingCriteriaFilters(filterChipItems, dataFilters); + + // CTF -> Cost Driver DrillDowns + ApplyDrillDownFilters(dataFilters, isUsingAprdrg, drillDownCaseTypeFamily, drillDownCostDriver, forceIncludeOfCostDriverColumn, includeCodeColumn); + + dataFilters.AvailableDrgType = isUsingAprdrg ? StrataSphereCompare.Client.Models.Enums.DrgType.AprDrg : StrataSphereCompare.Client.Models.Enums.DrgType.MsDrg; + + return dataFilters; + } + + public FilterChipItem CreateCostDriverFilter() + { + return new FilterChipItem + { + ChipType = ChipType.CostDriver, + IsSelected = true, + Key = ChipKey.CostDriver, + Operator = OperatorType.In, + Filters = [.. ValidSphCostDrivers.Select(costDriver => new TreeNode + { + DisplayText = costDriver, + Id = costDriver + })] + }; + } + + #region Private Methods + + private async Task ProcessScoreMemberFilter(FilterChipItem filterChipItem, string isIn, List queryFilters, List encounterSummaryFilters, CancellationToken cancellationToken) + { + var dimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync(filterChipItem.GlobalId, cancellationToken); + if (dimension != null) + { + var members = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, filterChipItem.Filters.Select(f => f.Id), cancellationToken); + + var ids = members.Select(m => m.Id).ToList(); + + if (filterChipItem.GlobalId.Equals("Patient Type Rollup", StringComparison.InvariantCultureIgnoreCase)) + { + var patientTypeDimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Patient Type", cancellationToken); + var patientTypeMembers = await _schemaServiceClient.GetDimensionMembersByDimensionGuidAsync(patientTypeDimension.DimensionGuid, cancellationToken); + var patientTypeIds = patientTypeMembers.Where(n => ids.Contains(n.PatientTypeRollupID.ToString())).Select(m => m.PatientTypeID).ToList(); + ids = patientTypeIds.ConvertAll(x => x.ToString()); + } + + if (ids.Count == 0) { return; } + + var columnName = filterChipItem.Key.ToString(); + if (columnName.Equals("CaseTypeFamilyID", StringComparison.InvariantCultureIgnoreCase)) + { + queryFilters.Add($"el.CaseTypeFamilyID {isIn} ({string.Join(", ", ids)})"); + } + else if (columnName.Equals("MSDRGRollupID", StringComparison.InvariantCultureIgnoreCase)) + { + queryFilters.Add($"dmsdroll.MSDRGRollupID {isIn} ({string.Join(", ", ids)})"); + } + else + { + if (filterChipItem.Key.IsEncounterSummaryFilter()) + { + encounterSummaryFilters.Add($"fpes.{columnName} {isIn} ({string.Join(", ", ids)})"); + } + else + { + queryFilters.Add($"el.{columnName} {isIn} ({string.Join(", ", ids)})"); + } + } + } + } + + private static void ProcessBooleanFilter(FilterChipItem filterChipItem, string isIn, List queryFilters) + { + queryFilters.Add(filterChipItem.Key switch + { + ChipKey.IsGreaterThanZeroCharges => $"el.Charges {(filterChipItem.IsSelected.Value ? ">" : " <= ")} 0", + ChipKey.Mortality => $"el.isExpired {isIn} (" + (filterChipItem.IsSelected.Value ? "1" : "0") + ")", + _ => null + }); + } + + private static void ProcessSpecialFilter(FilterChipItem filterChipItem, string isIn, List queryFilters) + { + var filterIds = string.Join(", ", filterChipItem.Filters.Select(f => f.Id)); + queryFilters.Add(filterChipItem.Key switch + { + ChipKey.RiskOfMortality => $"el.ROM {isIn} ({filterIds})", + ChipKey.SeverityOfIllness => $"el.SOI {isIn} ({filterIds})", + _ => null + }); + } + + private static void ProcessCostDriverFilter(FilterChipItem filterChipItem, List queryFilters) + { + var costDrivers = string.Join(", ", filterChipItem.Filters.Select(f => $"'{(f.Id == Constants.NOT_SPECIFIED ? "" : f.Id)}'")); + queryFilters.Add(filterChipItem.Key switch + { + // Cost driver only supports IN for now + ChipKey.CostDriver => $"el.CostDriver IN ({costDrivers})", + _ => null + }); + } + + private static void ProcessDateRangeFilter(FilterChipItem filterChipItem, IQueryParamBase queryParams) + { + var dateRange = filterChipItem.DateRange.Select(dr => dr.Date).ToArray(); + queryParams.DischargeDateStart = $"{dateRange[0]:yyyy-MM-dd}"; + queryParams.DischargeDateEnd = $"{dateRange[1].AddDays(1):yyyy-MM-dd}"; + } + + private async Task ApplyPopulationCriteriaFiltersAsync(bool isUsingAprdrg, CostDetailsFilterDto dataFilters, List filterChipItems, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + + await ApplyScoreMemberFiltersAsync(filterChipItems, isUsingAprdrg, dbContext, dataFilters, cancellationToken); + ApplyCostDriverFilters(filterChipItems, dataFilters); + } + + private void ApplyBenchmarkingCriteriaFilters(IEnumerable filterChipItems, CostDetailsFilterDto dataFilters) + { + if (!filterChipItems.Any()) { return; } + + // New Born Flag (boolean type) + var isNewBornFilterItem = filterChipItems.FirstOrDefault(f => f.Key == ChipKey.SPHIsNewBorn); + if (isNewBornFilterItem != null) + { + dataFilters.SphIsNewborn = isNewBornFilterItem.Filters.Any(x => x.Id.Equals("true", StringComparison.OrdinalIgnoreCase)); + } + + // Apply filters for multiple selection types + var filterMappings = new Dictionary>> + { + { ChipKey.SPHAgeRollup, ids => dataFilters.SphAgeRollup = ids }, + { ChipKey.SPHDischargeStatus, ids => dataFilters.SphDischargeStatus = ids }, + { ChipKey.SPHAdmitType, ids => dataFilters.SphAdmitType = ids }, + { ChipKey.SPHGender, ids => dataFilters.SphGender = ids }, + { ChipKey.SPHPatientType, ids => dataFilters.SphPatientType = ids } + }; + + ApplyMultiSelectFilters(filterChipItems, filterMappings); + } + + private static void ApplyMultiSelectFilters(IEnumerable filterChipItems, Dictionary>> filterMappings) + { + foreach (var mapping in filterMappings) + { + var filterItems = filterChipItems.Where(f => f.Key == mapping.Key); + if (filterItems.Any()) + { + var ids = filterItems.SelectMany(x => x.Filters.Select(filter => filter.Id)).ToList(); + mapping.Value(ids); + } + } + } + + private static void ApplyCostDriverFilters(IEnumerable filterChipItems, CostDetailsFilterDto dataFilters) + { + // ChipType.CostDriver + var costDriverFilterItems = filterChipItems.Where(f => f.Key == ChipKey.CostDriver); + if (costDriverFilterItems.Any()) + { + dataFilters.SphChargeCodeCostDriver = costDriverFilterItems + .SelectMany(i => i.Filters.Select(x => x.Id)) // id = displaytext for this type + .ToList(); + } + } + + /// + /// Configures the filters for drilling down + /// + /// the object to modify to apply the drill down + /// if set, will filter the returned data down to specific CTF + /// For page 2, set and will filter specifically down to a costdriver + /// Only used when set to true if is null and is an empty string, includes all other and total columns to cost driver if true and costdriver filters are set + /// forces the load of the lowest level detail for the cpt, drg columns + private static void ApplyDrillDownFilters(CostDetailsFilterDto dataFilters, bool isUsingAprDrg, CaseTypeFamily drillDownCaseTypeFamily, string drillDownCostDriver, bool forceIncludeOfCostDriverColumn, bool includeCodeColumn) + { + if (drillDownCaseTypeFamily != null) + { + // Apply CTF drill down filter if the CTF is one of the allowed types. + var allowedDetailTypes = new string[] { "msdrg", "aprdrg", "cpt" }; + var detailType = drillDownCaseTypeFamily.CaseTypeFamilyType.ToLower(); + if (allowedDetailTypes.Contains(detailType)) + { + dataFilters.CostDetailsFilterDrillDown = new CostDetailsFilterDrillDownDto + { + DetailType = detailType == "cpt" ? "cpt" : isUsingAprDrg ? "aprdrg" : "msdrg", // case sensitive (must be lowercase) + DetailFilterValue = drillDownCaseTypeFamily.Name + }; + + // Attempt to apply Cost Driver filter if CTF filter is applied. + if (!string.IsNullOrEmpty(drillDownCostDriver)) + { + dataFilters.CostDetailsFilterDrillDown.CostDriverFilterValue = drillDownCostDriver; + } + } + } + else if (forceIncludeOfCostDriverColumn) + { + dataFilters.CostDetailsFilterDrillDown = new CostDetailsFilterDrillDownDto(); //this forces the costdriver column to be included - we should look at a more explicit way to do this in the cost detail api + if (dataFilters.SphChargeCodeCostDriver.Count > 0) + { + dataFilters.IncludeCostDriverAggregations = true; + } + } + + dataFilters.IncludeCodeColumn = includeCodeColumn; + } + + private async Task ApplyScoreMemberFiltersAsync(IEnumerable filterChipItems, bool isUsingAprdrg, JazzDbContext dbContext, CostDetailsFilterDto dataFilters, CancellationToken cancellationToken) + { + var filterMappings = new List + { + new(ChipKey.AprDrgID, async memberIds => + dataFilters.SphAprDrgCode = await dbContext.DimAprDrgs + .Where(x => memberIds.Contains(x.AprDrgId)) + .Select(x => x.AprDrgCode) + .ToListAsync(cancellationToken)), + + new(ChipKey.MSDRGRollupID, async memberIds => + dataFilters.SphMsDrgCode = await dbContext.DimMsdrgRollups + .Where(x => memberIds.Contains(x.MsdrgRollupId)) + .Select(x => x.MsdrgRollupCode) + .ToListAsync(cancellationToken)), + + new(ChipKey.ICD10DXPrimaryDiagID, async memberIds => + dataFilters.SphPrimaryIcd10DxCode = await dbContext.DimIcd10Dxs + .Where(x => memberIds.Contains(x.Icd10DxId)) + .Select(x => x.Icd10DxCode) + .ToListAsync(cancellationToken)), + + new(ChipKey.ICD10PXPrimaryID, async memberIds => + dataFilters.SphPrimaryIcd10PxCode = await dbContext.DimIcd10Pxs + .Where(x => memberIds.Contains(x.Icd10PxId)) + .Select(x => x.Icd10PxCode) + .ToListAsync(cancellationToken)), + + new(ChipKey.PrimaryCPTID, async memberIds => + dataFilters.SphPrimaryCptCode = await dbContext.DimCpts + .Where(x => memberIds.Contains(x.CptId)) + .Select(x => x.CptCode) + .ToListAsync(cancellationToken)) + }; + + // Apply standard score member filters + await ApplyStandardScoreMemberFiltersAsync(filterChipItems, filterMappings, cancellationToken); + + // Handle special case for Case Type Family Names + await ApplyCaseTypeFamilyFilterAsync(filterChipItems, isUsingAprdrg, dbContext, dataFilters, cancellationToken); + } + + private async Task ApplyStandardScoreMemberFiltersAsync(IEnumerable filterChipItems, List filterMappings, CancellationToken cancellationToken) + { + var tasks = filterMappings + .Select(async mapping => + { + var filterItems = filterChipItems.Where(f => f.Key == mapping.ChipKey); + if (filterItems.Any()) + { + var memberIds = await GetFilterChipMemberIdsAsync(filterItems, cancellationToken); + await mapping.ApplyFilter(memberIds); + } + }); + + await Task.WhenAll(tasks); + } + + private async Task ApplyCaseTypeFamilyFilterAsync(IEnumerable filterChipItems, bool isUsingAprdrg, JazzDbContext dbContext, CostDetailsFilterDto dataFilters, CancellationToken cancellationToken) + { + var caseTypeFamilyFilterItems = filterChipItems.Where(f => f.Key == ChipKey.CaseTypeFamilyID); + if (!caseTypeFamilyFilterItems.Any()) return; + + var memberIds = await GetFilterChipMemberIdsAsync(caseTypeFamilyFilterItems, cancellationToken); + var caseTypeFamilies = await dbContext.CaseTypeFamilies + .Where(ctf => memberIds.Contains(ctf.CaseTypeFamilyId)) + .ToListAsync(cancellationToken); + + + if (isUsingAprdrg) + { + dataFilters.AprDrgCaseTypeFamilyName = caseTypeFamilies + .Select(x => x.Name) + .ToList(); + } + else + { + dataFilters.MsDrgCaseTypeFamilyName = caseTypeFamilies + .Select(x => x.Name) + .ToList(); + } + + dataFilters.SphCptCodeCaseTypeFamilyName = caseTypeFamilies + .Where(x => x.CaseTypeFamilyType.Equals("cpt", StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Name) + .ToList(); + } + + private async Task> GetFilterChipMemberIdsAsync(IEnumerable filterItems, CancellationToken cancellationToken) + { + if (filterItems == null || !filterItems.Any()) + { + return new List(); + } + + var hPaths = filterItems.SelectMany(items => items.Filters.Select(filter => filter.Id)); + var globalId = filterItems.FirstOrDefault().GlobalId; + + var dimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync(globalId); + var members = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, hPaths, cancellationToken); + var memberIds = members.Select(m => int.Parse(m.Id)).Distinct().ToList(); + return memberIds; + } + + private record ScoreMemberFilterMapping(ChipKey ChipKey, Func, Task> ApplyFilter); + + #endregion + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationInfo.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationInfo.cs new file mode 100644 index 0000000..22617dc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationInfo.cs @@ -0,0 +1,31 @@ +using Strata.ContinuousImprovement.Biz.Exploration.Benchmark; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes; +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public class ExplorationInfo : IExplorationInfo + { + public Guid ConfigurationGuid { get; set; } + public ExclusionCriteria ExclusionCriteria { get; set; } + public Filters.Filter Filters { get; set; } + public int PeerGroupId { get; set; } + public BenchmarkingInsightsGranularity Granularity { get; set; } + } + + public class ExplorationCostDriverInfo : ExplorationInfo + { + public int CaseTypeFamilyId { get; set; } + } + + public class ExplorationDetailInfo : ExplorationInfo + { + public int CaseTypeFamilyId { get; set; } + public string CostDriver { get; set; } + public CaseTypeCategories? CodeType { get; set; } + public List ExcludedEncounters { get; set; } + public List IncludedEncounters { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationService.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationService.cs new file mode 100644 index 0000000..37f6706 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationService.cs @@ -0,0 +1,793 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Exploration.Benchmark; +using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.PeerGroups; +using Strata.ContinuousImprovement.Biz.PeerGroups.Enums; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using Strata.StrataSphereCompare.Client; +using Strata.StrataSphereCompare.Client.Models; +using Strata.StrataSphereCompare.Client.Models.Enums; +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public class ExplorationService(IAsyncDbContextFactory dbContextFactory, + ISnowflakeDatabaseContext snowflakeDatabaseContext, + IFiscalMonthResolver fiscalMonthResolver, + IFeatureFlagWrapper featureFlagWrapper, + IStrataSphereCompareService strataSphereCompareService, + IExplorationFilterService explorationFilterService, + ILogger logger) : IExplorationService + { + public async Task> GetCaseTypeFamilySummariesAsync(IExplorationInfo exploration, CancellationToken cancellationToken) + { + Stopwatch performanceTimer = null; + var isFlexExPerfLoggingEnabled = await IsFlexExPerfLogging(cancellationToken); + + var isUsingAprdrg = await IsUsingAprdrgAsync(cancellationToken); + + var queryParams = new QueryParams + { + CodeType = isUsingAprdrg ? CaseTypeCategories.APRDRG : CaseTypeCategories.MSDRG, + LosExclusionType = exploration.ExclusionCriteria.LosExclusionType.ToString(), + LosExclusionValue = exploration.ExclusionCriteria.LosExclusionValue.ToString(), + CostExclusionType = exploration.ExclusionCriteria.CostExclusionType.ToString(), + CostExclusionValue = exploration.ExclusionCriteria.CostExclusionValue.ToString(), + }; + + var filters = await explorationFilterService.GetFilterStringsAsync(exploration.Filters.FilterChipItems, queryParams, cancellationToken); + var ctfSummaryQuery = ExplorationServiceQuery.BuildCaseTypeFamilySummaryQuery(filters.QueryFilters, filters.EncounterSummaryFilters); + + try + { + var start = DateTime.Now; + + // Fire both async operations concurrently + + var ctfSummaryTask = snowflakeDatabaseContext + .QueryAsync(ctfSummaryQuery, "CaseTypeFamily Summary", queryParams); + var benchmarkingTask = GetBenchmarkingDataForSummaries(exploration.PeerGroupId, exploration.Filters, isUsingAprdrg, cancellationToken); + + if (isFlexExPerfLoggingEnabled) performanceTimer = Stopwatch.StartNew(); + await Task.WhenAll(ctfSummaryTask, benchmarkingTask); + if (isFlexExPerfLoggingEnabled) logger.LogPerformanceInformation($"CI FlexEx Perf * Call: Task.WhenAll * Elapsed time: {performanceTimer.Elapsed}"); + + var ctfSummaryInfo = ctfSummaryTask.Result; + var benchmarkingData = benchmarkingTask.Result; + + // CTF Summaries + var caseTypeFamilySummaries = ctfSummaryInfo.Select(caseTypeGroup => new CaseTypeFamilySummaryData(caseTypeGroup)).ToList(); + + var endOfQuery = DateTime.Now.Subtract(start).TotalMilliseconds; + logger.LogServiceInformation("GetCaseTypeFamilySummariesAsyncLogging", new Dictionary { + { "query", ctfSummaryQuery }, + { "queryParams", queryParams.AsDictionary }, + { "time", endOfQuery }, + { "rows", ctfSummaryInfo.Count() } + }); + + PopulateBenchmarkingSummaryData(caseTypeFamilySummaries, benchmarkingData, isUsingAprdrg); + + return caseTypeFamilySummaries.OrderBy(caseTypeFamilySummary => caseTypeFamilySummary.Name); + } + catch (Exception ex) + { + logger.LogServiceError(ex, info: new Dictionary { { "query", ctfSummaryQuery }, { "queryParams", queryParams } }); + throw; + } + } + + public async Task> GetCaseTypeFamilySummaryDetailsAsync(IExplorationInfo exploration, CancellationToken cancellationToken) + { + Stopwatch performanceTimer = Stopwatch.StartNew(); + var isFlexExPerfLoggingEnabled = await IsFlexExPerfLogging(cancellationToken); + + var isUsingAprdrg = await IsUsingAprdrgAsync(cancellationToken); + + var withChildren = true; + var detail = exploration as ExplorationCostDriverInfo; + + var queryParams = new QueryParams + { + CodeType = isUsingAprdrg ? CaseTypeCategories.APRDRG : CaseTypeCategories.MSDRG, + CaseTypeFamilyId = detail.CaseTypeFamilyId + }; + + var filters = await explorationFilterService.GetFilterStringsAsync(exploration.Filters.FilterChipItems, queryParams, cancellationToken); + + var ctfSummaryQuery = ExplorationServiceQuery.BuildCaseTypeFamilySummaryDetailQuery(filters.QueryFilters, filters.EncounterSummaryFilters); + + try + { + await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + + var caseTypeFamily = await dbContext.CaseTypeFamilies.FirstOrDefaultAsync(x => x.CaseTypeFamilyId == detail.CaseTypeFamilyId, cancellationToken: cancellationToken); + + // Fire both async operations concurrently + var ctfCostDriverTask = snowflakeDatabaseContext + .QueryAsync(ctfSummaryQuery, "CaseTypeFamily CostDriver Summary", queryParams); + var benchmarkingTask = GetBenchmarkingDataForCaseTypeFamily(exploration.PeerGroupId, exploration.Filters, caseTypeFamily, isUsingAprdrg, cancellationToken); + + if (isFlexExPerfLoggingEnabled) performanceTimer = Stopwatch.StartNew(); + await Task.WhenAll(ctfCostDriverTask, benchmarkingTask); + if (isFlexExPerfLoggingEnabled) { logger.LogPerformanceInformation($"CI FlexEx Perf * Call: Task.WhenAll * Elapsed time: {performanceTimer.Elapsed}"); } + + var ctfCostDriverInfo = ctfCostDriverTask.Result; + var benchmarkingData = benchmarkingTask.Result; + + // Tree Grid Details + var caseTypeGroups = ctfCostDriverInfo.GroupBy(x => x.CaseTypeFamilyId.ToString(), (CaseTypeFamilyId, Items) => new { CaseTypeFamilyId, Items }); + var caseTypeGroup = caseTypeGroups.FirstOrDefault(); + if (caseTypeGroup == null) { return Enumerable.Empty(); } + + var ctfSummaryDetail = new CaseTypeFamilySummaryData(caseTypeGroup.CaseTypeFamilyId, caseTypeGroup.Items, exploration.ExclusionCriteria, withChildren: withChildren); + + PopulateBenchmarkingCostDriverData(ctfSummaryDetail, benchmarkingData); + + logger.LogServiceInformation("GetCaseTypeFamilySummaryDetailsAsyncLogging", new Dictionary { + { "query", ctfSummaryQuery }, + { "queryParams", queryParams.AsDictionary } + }); + + return new List + { + ctfSummaryDetail + }; + } + catch (Exception ex) + { + logger.LogServiceError(ex, info: new Dictionary { { "query", ctfSummaryQuery }, { "queryParams", queryParams } }); + throw; + } + } + + /// + /// Gets heatmap data for summary page based on the current filters + /// + /// filters from the UI + /// Row granularity: CaseTypeFamily, DrgCode, or PrimaryCpt + /// Token to cancel the asynchronous operation. + /// + public async Task> GetBenchmarkingDataForHeatMapAsync(IExplorationInfo exploration, BenchmarkingInsightsGranularity granularity, CancellationToken cancellationToken) + { + var isUsingAprdrg = await IsUsingAprdrgAsync(cancellationToken); + // Only apply filter if we don't filter by cost driver + if (!exploration.Filters.FilterChipItems.Any(fc => fc.Key == ChipKey.CostDriver)) + { + exploration.Filters.FilterChipItems.Add(explorationFilterService.CreateCostDriverFilter()); + } + + // Guard: if the code level enhancement flag is off, fall back to CaseTypeFamily + if (granularity != BenchmarkingInsightsGranularity.CaseTypeFamily) + { + granularity = BenchmarkingInsightsGranularity.CaseTypeFamily; + } + + var isCodeLevel = granularity != BenchmarkingInsightsGranularity.CaseTypeFamily; + var benchmarkingData = await GetBenchmarkingData(exploration.PeerGroupId, exploration.Filters, caseTypeFamily: null, "", isUsingAprdrg, true, isCodeLevel, cancellationToken); + + // Map to simplified HeatMapData using the appropriate mapper based on granularity level + var heatMapData = granularity switch + { + BenchmarkingInsightsGranularity.DrgCode => HeatMapDataMapper.ToHeatMapDataCodeLevel(benchmarkingData, isUsingAprdrg), + BenchmarkingInsightsGranularity.PrimaryCpt => HeatMapDataMapper.ToHeatMapDataPrimaryCptLevel(benchmarkingData), + _ => HeatMapDataMapper.ToHeatMapData(benchmarkingData, isUsingAprdrg) + }; + + return heatMapData.OrderBy(h => h.Name).ToList(); + } + + /// + /// Gets benchmarking data for all case type families (summary view) + /// Page 1 + /// + private Task> GetBenchmarkingDataForSummaries(int peerGroupId, Filters.Filter filters, bool isUsingAprdrg, CancellationToken cancellationToken) + { + return GetBenchmarkingData(peerGroupId, filters, caseTypeFamily: null, costDriver: null, isUsingAprdrg, false, false, cancellationToken); + } + + /// + /// Gets benchmarking data for a specific case type family (includes cost driver breakdown) + /// Page 1 + /// + private Task> GetBenchmarkingDataForCaseTypeFamily(int peerGroupId, Filters.Filter filters, CaseTypeFamily caseTypeFamily, bool isUsingAprdrg, CancellationToken cancellationToken) + { + return GetBenchmarkingData(peerGroupId, filters, caseTypeFamily, costDriver: null, isUsingAprdrg, false, false, cancellationToken); + } + + /// + /// Gets benchmarking data for a specific case type family and cost driver combination (detail view) + /// This is for page 2 + /// + private Task> GetBenchmarkingDataForCostDriver(int peerGroupId, Filters.Filter filters, CaseTypeFamily caseTypeFamily, string costDriver, bool isUsingAprdrg, bool includeCodeColumn, CancellationToken cancellationToken) + { + return GetBenchmarkingData(peerGroupId, filters, caseTypeFamily, costDriver, isUsingAprdrg, false, includeCodeColumn, cancellationToken); + } + + /// + /// Retrieves benchmarking cost data based on peer group criteria and optional filters. + /// + /// The identifier of the peer group to use for benchmarking comparison. + /// The filter criteria to apply to the benchmarking data query. + /// Optional case type family to filter results. Pass null for summary-level data. + /// Optional cost driver to filter results. Pass null unless drilling down to cost driver level. + /// True if using APR-DRG classification; false for MS-DRG classification. + /// Only used when set to true if is null and is an empty string + /// forces the override of including the code column (msdrg, aprdrg, cpt) + /// Token to cancel the asynchronous operation. + /// A list of cost detail records from the benchmarking service, or an empty list if benchmarking is disabled or no peer group mappings exist. + private async Task> GetBenchmarkingData(int peerGroupId, Filters.Filter filters, CaseTypeFamily caseTypeFamily, string costDriver, bool isUsingAprdrg, bool forceIncludeOfCostDriverColumn, bool includeCodeColumn, CancellationToken cancellationToken) + { + var isBenchmarkingEnabled = await featureFlagWrapper.IsCiBenchmarkingEnabled(); + if (!isBenchmarkingEnabled) return new List(); + + await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + + var peerGroupMappings = await dbContext.PeerGroupCriteriaMappings.Where(x => x.PeerGroupId == peerGroupId).ToListAsync(cancellationToken); + if (peerGroupMappings == null || peerGroupMappings.Count == 0) return new List(); + var cohortParams = BuildCohortParams(peerGroupMappings, filters); + + var dataFilters = await explorationFilterService.BuildDataFiltersAsync(caseTypeFamily, costDriver, filters, isUsingAprdrg, forceIncludeOfCostDriverColumn, includeCodeColumn, cancellationToken); + var benchmarkingData = await strataSphereCompareService.GetCostDetailsAsync(cohortParams, dataFilters, cancellationToken); + + return benchmarkingData?.ToList() ?? new List(); + } + + private static BaseComparisonParams BuildCohortParams(IEnumerable mappings, Filters.Filter filters) + { + var comparisonParams = new BaseComparisonParams(); + PopulatePeerGroupCriteria(comparisonParams, mappings); + PopulateDateRange(comparisonParams, filters); + return comparisonParams; + } + + private static void PopulatePeerGroupCriteria(BaseComparisonParams comparisonParameters, IEnumerable mappings) + { + if (mappings == null) return; + + var hospitalTypes = new HashSet(); + var censusRegions = new HashSet(); + var operatingExpenses = new HashSet(); + var isAmcValues = new HashSet(); + var bedSizes = new HashSet(); + var urbanRuralValues = new HashSet(); + + foreach (var mapping in mappings) + { + var criteriaType = (PeerGroupCritetiaType)mapping.MappingType; + switch (criteriaType) + { + case PeerGroupCritetiaType.EntityTypes: + hospitalTypes.Add((HospitalType)mapping.MappingValue); + break; + case PeerGroupCritetiaType.CensusRegions: + censusRegions.Add((CensusRegion)mapping.MappingValue); + break; + case PeerGroupCritetiaType.OperatingExpenses: + operatingExpenses.Add((OperatingExpense)mapping.MappingValue); + break; + case PeerGroupCritetiaType.AMCTypes: + isAmcValues.Add((IsAmc)mapping.MappingValue); + break; + case PeerGroupCritetiaType.BedSize: + bedSizes.Add((BedSize)mapping.MappingValue); + break; + case PeerGroupCritetiaType.UrbanRural: + urbanRuralValues.Add((UrbanRural)mapping.MappingValue); + break; + } + } + + if (hospitalTypes.Count > 0) comparisonParameters.HospitalType = hospitalTypes.ToArray(); + if (censusRegions.Count > 0) comparisonParameters.CensusRegion = censusRegions.ToArray(); + if (operatingExpenses.Count > 0) comparisonParameters.OperatingExpense = operatingExpenses.ToArray(); + if (isAmcValues.Count > 0) comparisonParameters.IsAmc = isAmcValues.ToArray(); + if (bedSizes.Count > 0) comparisonParameters.BedSize = bedSizes.ToArray(); + if (urbanRuralValues.Count > 0) comparisonParameters.UrbanRural = urbanRuralValues.ToArray(); + } + + private static void PopulateDateRange(BaseComparisonParams comparisonParameters, Filters.Filter filters) + { + if (filters?.FilterChipItems == null) return; + + var dateFilter = filters.FilterChipItems.FirstOrDefault(f => f.ChipType == ChipType.DateRange); + + if (dateFilter?.DateRange == null || dateFilter.DateRange.Count() != 2) return; + + var start = dateFilter.DateRange.First(); + var end = dateFilter.DateRange.Last(); + + // benchmarking expects this format + comparisonParameters.StartDate = start.ToString("yyyyMM"); + comparisonParameters.EndDate = end.ToString("yyyyMM"); + } + + private static void PopulateBenchmarkingSummaryData(List summaries, List benchmarkingData, bool isUsingAprdrg) + { + if (benchmarkingData != null && benchmarkingData.Count > 0) + { + var benchmarkingLookup = benchmarkingData + .SelectMany(data => new[] + { + (Name: data.SphCptCodeCaseTypeFamilyName, Item: data), + isUsingAprdrg ? (Name: data.AprDrgCaseTypeFamilyName, Item: data) : (Name: data.MsDrgCaseTypeFamilyName, Item: data) + }) + .Where(x => x.Item != null + && !string.IsNullOrEmpty(x.Name) + && !x.Name.Equals("Not Specified")) // note: excluding "Not Specified" because we don't show it in the CTF summaries + .GroupBy(x => x.Name) + .ToDictionary(g => g.Key, g => g.Select(x => x.Item).FirstOrDefault()); + + summaries.ForEach(ctf => + { + if (benchmarkingLookup.TryGetValue(ctf.Name, out var benchmark)) + { + ctf.Data.PeerMedianCost = (double)benchmark.P50th_VariableDirectCost; + ctf.Data.PeerMedianALos = (double)benchmark.P50th_Alos; + ctf.Data.CustomerBenchmarkMedianCost = (double)(benchmark.CustomerP50th_VariableDirectCost ?? 0); + ctf.Data.CustomerBenchmarkALos = (double)(benchmark.CustomerP50th_Alos ?? 0); + } + }); + } + } + + private static void PopulateBenchmarkingCostDriverData(CaseTypeFamilySummaryData summary, List benchmarkingData) + { + if (benchmarkingData != null && benchmarkingData.Count > 0) + { + var costDriverLookup = benchmarkingData + .GroupBy(d => (d.SphChargeCodeCostDriver ?? string.Empty).Trim()) // intentionally includes "" costdriver grouping for not specified cost drivers + .Where(g => !string.IsNullOrEmpty(g.Key) || g.Any(x => string.IsNullOrEmpty(x.SphChargeCodeCostDriver))) + .ToDictionary(g => g.Key, g => g.First()); + + if (summary.Children != null && summary.Children.Any()) + { + foreach (var costDriverSummary in summary.Children) + { + if (costDriverLookup.TryGetValue(costDriverSummary.Name, out var benchmark)) + { + costDriverSummary.Data.PeerMedianCost = (double)benchmark.P50th_VariableDirectCost; + costDriverSummary.Data.PeerMedianALos = (double)benchmark.P50th_Alos; + costDriverSummary.Data.CustomerBenchmarkMedianCost = (double)(benchmark.CustomerP50th_VariableDirectCost ?? 0); + costDriverSummary.Data.CustomerBenchmarkALos = (double)(benchmark.CustomerP50th_Alos ?? 0); + } + } + } + } + } + + private static void PopulateBenchmarkingCostDriverDetailData(List details, bool isUsingAprdrg, List benchmarks, List benchmarkCodes) + { + if (details == null || !details.Any()) return; + if ((benchmarks == null || benchmarks.Count == 0) && (benchmarkCodes == null || benchmarkCodes.Count == 0)) + { + return; + } + + var benchmarkCodesLookup = benchmarkCodes.SelectMany(data => new[] + { + (Name: data.SphPrimaryCptCode, Item: data), + isUsingAprdrg ? (Name: data.SphAprDrgCode, Item: data) : (Name: data.SphMsDrgCode, Item: data) + }).Where(x => x.Item != null + && !string.IsNullOrEmpty(x.Name) + && !x.Name.Equals("Not Specified")) // note: excluding "Not Specified" because we don't show it in the CTF summaries + .GroupBy(x => x.Name) + .ToDictionary(g => g.Key, g => g.Select(x => x.Item).FirstOrDefault()); + + // map cost driver benchmarking data to first two levels + foreach (var detail in details) + { + if (detail.Data != null && detail.Children.Any()) + { + var benchmark = benchmarks.FirstOrDefault(); + if (benchmark != null) + { + detail.Data.PeerMedianCost = (double)benchmark.P50th_VariableDirectCost; + detail.Data.PeerMedianALos = (double)benchmark.P50th_Alos; + detail.Data.CustomerBenchmarkMedianCost = (double)(benchmark.CustomerP50th_VariableDirectCost ?? 0); + detail.Data.CustomerBenchmarkMedianALos = (double)(benchmark.CustomerP50th_Alos ?? 0); + } + + foreach (var child in detail.Children) + { + if (benchmarkCodesLookup.TryGetValue(child.Data.Id, out var benchmarkCode)) + { + child.Data.PeerMedianCost = (double)benchmarkCode.P50th_VariableDirectCost; + child.Data.PeerMedianALos = (double)benchmarkCode.P50th_Alos; + child.Data.CustomerBenchmarkMedianCost = (double)(benchmarkCode.CustomerP50th_VariableDirectCost ?? 0); + child.Data.CustomerBenchmarkMedianALos = (double)(benchmarkCode.CustomerP50th_Alos ?? 0); + } + } + } + } + } + + public async Task GetCaseTypeFamilyDetailsAsync(ExplorationDetailInfo exploration, CancellationToken cancellationToken) + { + var isUsingAprdrg = exploration.CodeType.HasValue + ? exploration.CodeType.Value == CaseTypeCategories.APRDRG + : await IsUsingAprdrgAsync(cancellationToken); Stopwatch performanceTimer = Stopwatch.StartNew(); + var isFlexExPerfLoggingEnabled = await IsFlexExPerfLogging(cancellationToken); + + var queryData = await EncountersQueryBuilder(exploration, isUsingAprdrg, ExplorationServiceQuery.CTFEntityCodePhysicianQuery, null, cancellationToken); + var queryOneParams = new Dictionary + { + { "MULTI_STATEMENT_COUNT", queryData.Item2 } + }; + + var ctfGroupCalcs = new CTFGroupCalcs(); + var entityGroupCalcs = Enumerable.Empty(); + var entityCodeGroupCalcs = Enumerable.Empty(); + var entityCodePhysicianGroupCalcs = Enumerable.Empty(); + var ctfEncounterDetails = Enumerable.Empty(); + + if (isFlexExPerfLoggingEnabled) performanceTimer = Stopwatch.StartNew(); + await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + var caseTypeFamily = await dbContext.CaseTypeFamilies.FirstOrDefaultAsync(x => x.CaseTypeFamilyId == exploration.CaseTypeFamilyId, cancellationToken); + if (isFlexExPerfLoggingEnabled) logger.LogPerformanceInformation($"CI FlexEx Perf * Call: dbContext.CaseTypeFamilies.FirstOrDefaultAsync * Elapsed time: {performanceTimer.Elapsed}"); + var benchmarkingData = new List(); + var benchmarkingDataCodes = new List(); + + try + { + // Fire both async operations concurrently + var detailQueryTask = snowflakeDatabaseContext.QueryMultipleAsync(queryData.Item1, "Entity Detail", queryOneParams); + var benchmarkingTask = GetBenchmarkingDataForCostDriver(exploration.PeerGroupId, exploration.Filters, caseTypeFamily, exploration.CostDriver, isUsingAprdrg, false, cancellationToken); + var benchmarkingCodesTask = GetBenchmarkingDataForCostDriver(exploration.PeerGroupId, exploration.Filters, caseTypeFamily, exploration.CostDriver, isUsingAprdrg, true, cancellationToken); //to get the drill down data + + if (isFlexExPerfLoggingEnabled) performanceTimer = Stopwatch.StartNew(); + await Task.WhenAll(detailQueryTask, benchmarkingTask, benchmarkingCodesTask); + if (isFlexExPerfLoggingEnabled) + { + logger.LogPerformanceInformation($"CI FlexEx Perf * Call: Task.WhenAll * Elapsed time: {performanceTimer.Elapsed}"); + performanceTimer = Stopwatch.StartNew(); + } + + + var detailQuery = detailQueryTask.Result; + benchmarkingData = benchmarkingTask.Result; + benchmarkingDataCodes = benchmarkingCodesTask.Result; + ctfGroupCalcs = detailQuery.Read().FirstOrDefault() ?? ctfGroupCalcs; + entityGroupCalcs = detailQuery.Read().ToList(); + entityCodeGroupCalcs = detailQuery.Read().ToList(); + entityCodePhysicianGroupCalcs = detailQuery.Read().ToList(); + ctfEncounterDetails = detailQuery.Read(); + ctfEncounterDetails.ForEach(x => { x.IdentifiedVariation = x.VariableDirectCost - ctfGroupCalcs.MedianVDC; }); + + if (isFlexExPerfLoggingEnabled) logger.LogPerformanceInformation($"CI FlexEx Perf * Getting all the detailQueries * Elapsed time: {performanceTimer.Elapsed}"); + + } + catch (Exception ex) + { + logger.LogServiceError(ex, info: new Dictionary { { "query", queryData.Item1 }, { "queryParams", queryOneParams } }); + throw; + } + + if (isFlexExPerfLoggingEnabled) performanceTimer = Stopwatch.StartNew(); + + var details = ctfEncounterDetails + .GroupBy(x => x.EntityId) + .Join(entityGroupCalcs, x => x.Key, y => y.EntityId, (x, y) => new { Group = x, GroupCalcs = y }) + .Select(entityGroup => new CaseTypeFamilyDetailData + { + Key = string.Join("-", "1", entityGroup.GroupCalcs.Id), + Data = new CaseTypeDetailRowData((IEnumerable)entityGroup.Group, entityGroup.GroupCalcs), + Children = entityGroup.Group + .GroupBy(x => x.Code) + .Join(entityCodeGroupCalcs, x => new { x1 = entityGroup.GroupCalcs.EntityId, x2 = x.Key }, y => new { x1 = y.EntityId, x2 = y.Code }, (x, y) => new { Group = x, GroupCalcs = y }) + .Select(entityCodeGroup => new CaseTypeFamilyDetailData + { + Key = string.Join("-", "2", entityGroup.GroupCalcs.Id, entityCodeGroup.GroupCalcs.Id), + Data = new CaseTypeDetailRowData((IEnumerable)entityCodeGroup.Group, entityCodeGroup.GroupCalcs), + Children = entityCodeGroup.Group + .GroupBy(x => x.PhysicianId) + .Join(entityCodePhysicianGroupCalcs, x => new { x1 = entityGroup.GroupCalcs.EntityId, x2 = entityCodeGroup.GroupCalcs.Code, x3 = x.Key }, y => new { x1 = y.EntityId, x2 = y.Code, x3 = y.PhysicianId }, (x, y) => new { Group = x, GroupCalcs = y }) + .Select(entityCodePhysicianGroup => new CaseTypeFamilyDetailData + { + Key = string.Join("-", "3", entityGroup.GroupCalcs.Id, entityCodeGroup.GroupCalcs.Id, entityCodePhysicianGroup.GroupCalcs.Id), + Data = new CaseTypeDetailRowData((IEnumerable)entityCodePhysicianGroup.Group, entityCodePhysicianGroup.GroupCalcs), + Children = entityCodePhysicianGroup.Group.Select(encounter => new CaseTypeFamilyDetailData + { + Key = string.Join("-", "4", entityGroup.GroupCalcs.Id, entityCodeGroup.GroupCalcs.Id, entityCodePhysicianGroup.GroupCalcs.Id, encounter.EncounterId), + Data = new CaseTypeDetailRowData(encounter) + }) + }) + }).ToList() + }).ToList(); + + if (isFlexExPerfLoggingEnabled) + { + logger.LogPerformanceInformation($"CI FlexEx Perf * SQL to get details * Elapsed time: {performanceTimer.Elapsed}"); + performanceTimer = Stopwatch.StartNew(); + } + + PopulateBenchmarkingCostDriverDetailData(details, isUsingAprdrg, benchmarkingData, benchmarkingDataCodes); + if (isFlexExPerfLoggingEnabled) + { + logger.LogPerformanceInformation($"CI FlexEx Perf * Call: PopulateBenchmarkingCostDriverDetailData * Elapsed time: {performanceTimer.Elapsed}"); + performanceTimer = Stopwatch.StartNew(); + } + + + var metrics = new CaseTypeFamilyMetric + { + TotalEncounters = ctfEncounterDetails.Count(), + IdentifiedVariation = ctfEncounterDetails.Where(ed => ed.IdentifiedVariation > 0).Sum(e => e.IdentifiedVariation), + EncountersIncluded = ctfEncounterDetails.Count(ed => !ed.IsExcluded), + VariationIncluded = ctfEncounterDetails.Where(ed => !ed.IsExcluded && ed.IdentifiedVariation > 0).Sum(e => e.IdentifiedVariation), + EncountersExcluded = ctfEncounterDetails.Count(ed => ed.IsExcluded), + VariationExcluded = ctfEncounterDetails.Where(ed => ed.IsExcluded && ed.IdentifiedVariation > 0).Sum(e => e.IdentifiedVariation) + }; + if (isFlexExPerfLoggingEnabled) + { + logger.LogPerformanceInformation($"CI FlexEx Perf * Getting metrics * Elapsed time: {performanceTimer.Elapsed}"); + performanceTimer = Stopwatch.StartNew(); + } + + var ctf = await dbContext.CaseTypeFamilies + .Where(ctf => ctf.CaseTypeFamilyId == exploration.CaseTypeFamilyId) + .SingleOrDefaultAsync(cancellationToken); + var ctfType = ctf?.CaseTypeFamilyType ?? CaseTypeFamily.CaseTypeFamilyTypeDefault; + if (isFlexExPerfLoggingEnabled) logger.LogPerformanceInformation($"CI FlexEx Perf * Getting ctf * Elapsed time: {performanceTimer.Elapsed}"); + + + return new CaseTypeFamilyDetailInfo + { + CaseTypeFamilyType = ctfType, + CaseTypeFamilyName = ctfGroupCalcs.CTFName, + Metrics = metrics, + CaseTypeFamilyDetails = details + }; + } + + public async Task> GetEncountersAsync(ExplorationDetailInfo exploration, Guid configurationGuid, CancellationToken cancellationToken) + { + + await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + var config = configurationGuid != Guid.Empty ? + + await dbContext.Configurations.SingleOrDefaultAsync(c => c.ConfigurationGuid == configurationGuid, cancellationToken) : null; + + if (config != null) + { + logger.LogServiceInformation($"{nameof(GetEncountersAsync)} - config is not null"); + config.FiscalMonthResolver = fiscalMonthResolver; + } + else + { + logger.LogServiceInformation($"{nameof(GetEncountersAsync)} - config is null"); + } + + var isUsingAprdrg = exploration.CodeType.HasValue && exploration.CodeType.Value == CaseTypeCategories.APRDRG; + + var queryData = await EncountersQueryBuilder(exploration, isUsingAprdrg, ExplorationServiceQuery.EncountersIsIncludedQuery, config, cancellationToken); + + logger.LogServiceInformation($"{nameof(GetEncountersAsync)}", new Dictionary { { "query", queryData.Item1 } }); + + var queryOneParams = new Dictionary + { + { "MULTI_STATEMENT_COUNT", queryData.Item2 } + }; + + var ctfGroupCalcs = new CTFGroupCalcs(); + var physicians = new Dictionary(); + var encounters = Enumerable.Empty(); + + try + { + var results = await snowflakeDatabaseContext.QueryMultipleAsync(queryData.Item1, "Entity Detail", queryOneParams); + physicians = results.Read().ToDictionary(x => x.PhysicianID, x => x); + encounters = results.Read().ToList(); + + logger.LogServiceInformation($"{nameof(GetEncountersAsync)} - physician count: {physicians.Count}, encounter count: {encounters.Count()}"); + } + catch (Exception ex) + { + logger.LogServiceError(ex, info: new Dictionary { { "query", queryData.Item1 }, { "queryParams", queryOneParams } }); + throw; + } + + var encounterPhysicians = new List(); + + foreach (var enc in encounters) + { + var encPhys = enc.MapToEncounterPhysicianInfo(); + if (physicians.TryGetValue(enc.PhysicianID, out var phys)) encPhys.AddPhysicianData(phys); + encounterPhysicians.Add(encPhys); + } + + logger.LogServiceInformation($"{nameof(GetEncountersAsync)} - encounterPhysicians count: {encounterPhysicians.Count}"); + + return encounterPhysicians; + } + + public async Task IsFlexExPerfLogging(CancellationToken cancellationToken) + { + return await featureFlagWrapper.IsCiFlexExPerformanceLoggingEnabled(); + } + + #region Internal Helpers + + // REMOVE these delegate methods - they are no longer needed: + // - GetFilters method + // - GetFilterStrings method + + private async Task<(string, byte)> EncountersQueryBuilder(ExplorationDetailInfo exploration, bool isUsingAprdrg, Func getQuery, + Configuration.Configuration configuration, CancellationToken cancellationToken) + { + + var trimQueryParams = new TrimQueryParams + { + CaseTypeFamilyId = exploration.CaseTypeFamilyId, + CostDriver = exploration.CostDriver + }; + + var dateStart = GetQueryStartDate(exploration, configuration); + + var dateEnd = GetQueryEndDate(exploration, configuration); + + trimQueryParams.DischargeDateStart = dateStart; + trimQueryParams.DischargeDateEnd = dateEnd; + + var trimQuery = ExplorationServiceQuery.CaseTypeFamilyTrimQuery(); + + + + CaseTypeFamilyTrimInfo trimInfo = new CaseTypeFamilyTrimInfo(); + try + { + var queryResult = await snowflakeDatabaseContext.QueryAsync(trimQuery, "CTF Trim Info", trimQueryParams); + if (queryResult.Any()) trimInfo = queryResult.First(); + } + catch (Exception ex) + { + logger.LogServiceError(ex, info: new Dictionary { { "query", trimQuery }, { "queryParams", trimQueryParams } }); + throw; + } + + var queryTrim = exploration.ExclusionCriteria.GetTrimQueryString(trimInfo); + var filterStrings = + await explorationFilterService.GetFilterStringsAsync(exploration.Filters.FilterChipItems + .Where(x => x.ChipType != ChipType.CostDriver), trimQueryParams, cancellationToken); + + var queryData = getQuery(filterStrings.QueryFilters, filterStrings.EncounterSummaryFilters, "", queryTrim); + var actualCodeTypes = new int[] { isUsingAprdrg ? (int)CaseTypeCategories.APRDRG : (int)CaseTypeCategories.MSDRG, (int)CaseTypeCategories.CPTCode }; + // Info: have problem with passing parameters to QueryMultipleAsync - it doesn't work. So as temporary solution it is prepared query without parameters + var parametersReplace = new Dictionary + { + { "@CaseTypeFamilyId", exploration.CaseTypeFamilyId.ToString() }, + { "@CostDriver", exploration.CostDriver == Constants.NOT_SPECIFIED ? "''" : $"'{exploration.CostDriver}'" }, + { "@DischargeDateStart", $"'{dateStart}'"}, + { "@DischargeDateEnd", $"'{dateEnd}'" }, + { "@EncounterExclusions", exploration.ExcludedEncounters.Count > 0 && configuration == null ? string.Join(", ", exploration.ExcludedEncounters) : "0" }, + { "@EncounterInclusions", exploration.IncludedEncounters.Count > 0 && configuration == null ? string.Join(", ", exploration.IncludedEncounters) : "0" }, + { "@CodeTypes", string.Join(", ", actualCodeTypes) } + }; + foreach (var x in parametersReplace) + { + queryData.Item1 = queryData.Item1.Replace(x.Key, x.Value); + } + + return queryData; + } + + private DateTime GetDischargeDateEnd(ExplorationDetailInfo exploration) + + { + var dischargeDateFilter = exploration.Filters.FilterChipItems + .SingleOrDefault((fci) => fci.Key == ChipKey.DischargeDateID); + return dischargeDateFilter?.DateRange.LastOrDefault() ?? DateTime.MaxValue; + } + + private string GetQueryEndDate(ExplorationDetailInfo exploration, Configuration.Configuration configuration) + { + var endDate = configuration != null ? configuration.GetTrackingEndDate() : GetDischargeDateEnd(exploration); + return endDate.AddDays(1).ToString("yyyy-MM-dd"); + } + + private string GetQueryStartDate(ExplorationDetailInfo exploration, Configuration.Configuration configuration) + + { + var dischargeDateFilter = exploration.Filters.FilterChipItems + .SingleOrDefault((fci) => fci.Key == ChipKey.DischargeDateID); + var dischargeDateStart = dischargeDateFilter?.DateRange.FirstOrDefault() ?? DateTime.MinValue; + var dischargeDateEnd = dischargeDateFilter?.DateRange.LastOrDefault() ?? DateTime.MaxValue; + if (configuration == null) + { + return $"{dischargeDateStart:yyyy-MM-dd}"; + } + // if discharge end date is less then tracking start date - we should take tracking start date + // otherwise we should take discharge end date so that baseline and tracking dates don't crossover + var trackingStartDate = dischargeDateEnd.AddDays(1) < configuration.GetTrackingStartDate(Generics.OpportunityTypes.Variation) + ? configuration.GetTrackingStartDate(Generics.OpportunityTypes.Variation) + : dischargeDateEnd.AddDays(1); + return $"{trackingStartDate:yyyy-MM-dd}"; + } + + internal async Task IsUsingAprdrgAsync(CancellationToken cancellationToken) + { + await using var dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + var ctfValueSetting = (await dbContext.SystemSettings.SingleOrDefaultAsync(item => item.Name == "Flexible Exploration Inpatient Case Type Family", cancellationToken)).Value; + return ctfValueSetting == "1"; + } + + #endregion + + + #region Internal Classes + + public interface IQueryParamBase + { + string DischargeDateEnd { get; set; } + string DischargeDateStart { get; set; } + } + + internal class QueryParamBase : IQueryParamBase + { + public string DischargeDateStart { get; set; } + public string DischargeDateEnd { get; set; } + public string LosExclusionType { get; set; } + public string LosExclusionValue { get; set; } + public string CostExclusionType { get; set; } + public string CostExclusionValue { get; set; } + } + + internal class QueryParams : QueryParamBase + { + public CaseTypeCategories CodeType { get; set; } + public int CaseTypeFamilyId { get; set; } + public Dictionary AsDictionary + => new Dictionary + { + { "CodeType", (byte)CodeType }, + { "DischargeDateStart", DischargeDateStart}, + { "DischargeDateEnd", DischargeDateEnd}, + { "LosExclusionType", LosExclusionType}, + { "LosExclusionValue", LosExclusionValue}, + { "CostExclusionType", CostExclusionType}, + { "CostExclusionValue", CostExclusionValue} + }; + } + + internal class TrimQueryParams : QueryParamBase + { + public int CaseTypeFamilyId { get; set; } + public string CostDriver { get; set; } + } + + public class FiltersDataListQuery + { + public IEnumerable Filters { get; set; } + public IEnumerable Joins { get; set; } + } + + public class FiltersDataQuery + { + public string Filter { get; set; } + public string Join { get; set; } + + public FiltersDataQuery() { } + public FiltersDataQuery(IEnumerable filters, IEnumerable joins) + { + Filter = filters.Any() ? "\r\n WHERE " + string.Join("\r\n AND ", filters) + "\r\n" : string.Empty; + Join = joins.Any() ? "\r\n" + string.Join("\r\n", joins) + "\r\n" : string.Empty; + } + } + + #endregion + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryBase.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryBase.sql new file mode 100644 index 0000000..9e47dad --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryBase.sql @@ -0,0 +1,61 @@ + +WITH EncounterList (EncounterID, CaseTypeFamilyId, ctftype, codeid, Charges, VariableDirectCost, IsExpired, ROM, SOI) AS ( + SELECT + fpes.EncounterID, + -- This should be optimized in next story + (CASE + WHEN dptr.Type = 'Outpatient' THEN fpes.CPTCodeCaseTypeFamilyID + WHEN 1 IN (@CodeTypes) THEN fpes.aprdrgcasetypefamilyid + ELSE fpes.msdrgcasetypefamilyid + END) AS CaseTypeFamilyId, + (CASE + WHEN dptr.Type = 'Outpatient' THEN 3 + WHEN 1 IN (@CodeTypes) THEN 1 + ELSE 0 + END) AS ctftype, + (CASE + WHEN dptr.Type = 'Outpatient' THEN fpes.primarycptid + WHEN 1 IN (@CodeTypes) THEN fpes.reportingaprdrgid + ELSE fpes.reportingdrgid + END) AS codeid, + SUM(c.charge) AS Charges, + SUM(c.variabledirectcost) AS VariableDirectCost, + CASE WHEN ds.DischargeStatusCode IN ('20', '40', '41', '42') THEN 1 ELSE 0 END AS IsExpired, + COALESCE(apr.aprrom, '0') AS ROM, + COALESCE(apr.aprsoi, '0') AS SOI + FROM clientDSS.FactPatientEncounterSummary fpes + LEFT JOIN cci.encounterchargecodecosts c on fpes.encounterid = c.encounterid + LEFT JOIN fw.DimChargeCode cc ON cc.chargecodeid = c.chargecodeid + JOIN fw.DimPatientType dpt ON dpt.PatientTypeID = fpes.PatientTypeID + JOIN fw.DimPatientTypeRollup dptr ON dptr.PatientTypeRollupID = dpt.PatientTypeRollupId + JOIN clientdss.DimDischargeStatus ds ON fpes.DischargeStatusID = ds.DischargeStatusId + LEFT JOIN dss.dimaprdrg apr ON fpes.apdrgid = apr.aprdrgid + WHERE fpes.dischargedatetime >= @DischargeDateStart AND fpes.dischargedatetime < @DischargeDateEnd + AND CaseTypeFamilyId = @CaseTypeFamilyId + AND cc.costdriver = @CostDriver + AND ctftype IN (@CodeTypes) + -- {encounterSummaryFilter} + GROUP BY fpes.EncounterId, CaseTypeFamilyId, ctftype, codeid, isExpired, rom, soi +), + +ListOfEncounters AS ( + SELECT + el.EncounterID, + el.CaseTypeFamilyId, + el.ctftype, + el.Charges, + el.VariableDirectCost, + el.IsExpired, + CASE WHEN (fpes.encounterid IN (@EncounterExclusions) + -- {queryTrim} + ) AND fpes.encounterid NOT IN (@EncounterInclusions) THEN 1 ELSE 0 END AS IsExcluded, + el.ROM, + el.SOI + -- {queryAdditionalColumns} + FROM clientDSS.FactPatientEncounterSummary fpes + JOIN EncounterList el ON fpes.encounterId = el.encounterid + LEFT JOIN dss.dimmsdrg dmsd ON dmsd.msdrgid = el.codeid and el.ctftype = 0 + LEFT JOIN dss.dimmsdrgrollup dmsdroll ON dmsdroll.msdrgrollupcode = dmsd.msdrgcode + -- {queryAdditionalJoins} +-- {queryFilter} +) \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryCodes.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryCodes.sql new file mode 100644 index 0000000..4fbcc17 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryCodes.sql @@ -0,0 +1,40 @@ + +SELECT + 1 as ctftype, + aprdrg.aprdrgid as codeid, + aprdrg.aprdrgcode as code, + t.codename as codename +FROM dss.DimAPRDRG aprdrg +INNER JOIN ( + SELECT + aprdrgcode as code, + reportingname as codename, + ROW_NUMBER() + OVER (PARTITION BY CODE ORDER BY drgschema DESC, aprdrgid DESC) AS rownumber + FROM dss.DimAPRDRG +) t on t.rownumber = 1 AND t.code = aprdrg.aprdrgcode +WHERE ctftype in (@CodeTypes) +UNION + SELECT + 0 as ctftype, + msdrg.msdrgid as codeid, + msdrg.msdrgcode as code, + t.codename as codename +FROM dss.DimMSDRG msdrg +INNER JOIN ( + SELECT + msdrgcode as code, + reportingname as codename, + ROW_NUMBER() + OVER (PARTITION BY CODE ORDER BY drgschema DESC, msdrgid DESC) AS rownumber + FROM dss.DimMSDRG +) t on t.rownumber = 1 AND t.code = msdrg.msdrgcode +WHERE ctftype in (@CodeTypes) +UNION +SELECT + 3 as ctftype, + cpt.cptid as codeid, + cpt.cptcode as code, + cpt.cptname as codename +FROM dss.DimCPT cpt +WHERE ctftype in (@CodeTypes) diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryEncountersData.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryEncountersData.sql new file mode 100644 index 0000000..63393f2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryEncountersData.sql @@ -0,0 +1,26 @@ +-- {queryListOfEncounters} + +SELECT + f.encounterid, + f.encounterrecordnumber, + + loe.ctftype, + + f.entityid, + loe.code, + f.primaryphysicianid as physicianid, + + f.lengthofstay, + loe.variabledirectcost, + loe.charges, + + loe.IsExpired, + loe.IsExcluded, + + ds.dischargeStatusCode, + ds.description AS dischargeStatusDescription + +FROM clientdss.factpatientencountersummary f + JOIN ListOfEncounters loe ON f.EncounterId = loe.EncounterId + JOIN clientdss.dimDischargeStatus ds ON ds.dischargeStatusId = f.dischargeStatusId +ORDER BY f.encounterid; diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryEncountersIdIncluded.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryEncountersIdIncluded.sql new file mode 100644 index 0000000..1dfc756 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryEncountersIdIncluded.sql @@ -0,0 +1,16 @@ +-- {queryListOfEncounters} + +SELECT + f.agecohortid, + f.dischargedatetime, + f.encounterid, + f.encounterrecordnumber, + de.fwentityid as entityid, + f.lengthofstay, + f.primaryphysicianid as physicianid, + f.servicelineid, + loe.IsExcluded +FROM clientdss.factpatientencountersummary f +JOIN ListOfEncounters loe ON f.EncounterId = loe.EncounterId +JOIN clientdss.dimEntity de ON de.entityid = f.entityid +ORDER BY f.encounterid; diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByCtf.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByCtf.sql new file mode 100644 index 0000000..ca8dde3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByCtf.sql @@ -0,0 +1,12 @@ +-- {queryListOfEncounters} + +SELECT ctf.CaseTypeFamilyID as ctfid, ctf.NAME as ctfname, IFNULL(t.medianvdc, 0) as medianvdc FROM( + SELECT + loe.CaseTypeFamilyId, + MEDIAN(loe.variabledirectcost) AS medianvdc + FROM clientdss.factpatientencountersummary f + JOIN ListOfEncounters loe ON f.encounterid = loe.encounterid + GROUP BY loe.CaseTypeFamilyId +) t +RIGHT JOIN dss.casetypefamily ctf ON ctf.CaseTypeFamilyID = t.CaseTypeFamilyId +WHERE ctf.CaseTypeFamilyID = @CaseTypeFamilyId; diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByEntity.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByEntity.sql new file mode 100644 index 0000000..43b1e18 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByEntity.sql @@ -0,0 +1,25 @@ +-- {queryListOfEncounters} + +SELECT + f.entityId, + e.name as entityname, + MAX(IFNULL(t.medianvdc, 0)) as medianvdc, + MAX(IFNULL(t.medianlos, 0)) medianlos, + MAX(IFNULL(t.averagelos, 0)) averagelos, + MAX(IFNULL(t.mediancharges, 0)) as mediancharges +FROM clientdss.factpatientencountersummary f +JOIN ListOfEncounters loe ON f.encounterid = loe.encounterid +JOIN clientdss.dimEntity e ON e.entityid = f.entityid +LEFT JOIN ( + SELECT + f.entityId, + MEDIAN(loe.variabledirectcost) AS medianvdc, + MEDIAN(f.lengthofstay) AS medianlos, + AVG(f.lengthofstay) AS averagelos, + MEDIAN(loe.charges) AS mediancharges + FROM clientdss.factpatientencountersummary f + JOIN ListOfEncounters loe ON f.encounterid = loe.encounterid + WHERE loe.IsExcluded = 0 + GROUP BY f.entityid +) t ON t.entityId = f.entityId +GROUP BY f.entityid, e.name; diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByEntityCode.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByEntityCode.sql new file mode 100644 index 0000000..aaaf18d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByEntityCode.sql @@ -0,0 +1,26 @@ +-- {queryListOfEncounters} + +SELECT + f.entityid, + loe.code, + loe.codename, + MAX(IFNULL(t.medianvdc, 0)) as medianvdc, + MAX(IFNULL(t.medianlos, 0)) medianlos, + MAX(IFNULL(t.averagelos, 0)) averagelos, + MAX(IFNULL(t.mediancharges, 0)) as mediancharges +FROM clientdss.factpatientencountersummary f +JOIN ListOfEncounters loe ON f.EncounterId = loe.EncounterId +LEFT JOIN ( + SELECT + f.entityid, + loe.code, + MEDIAN(loe.variabledirectcost) AS medianvdc, + MEDIAN(f.lengthofstay) AS medianlos, + AVG(f.lengthofstay) AS averagelos, + MEDIAN(loe.charges) AS mediancharges + FROM clientdss.factpatientencountersummary f + JOIN ListOfEncounters loe ON f.EncounterId = loe.EncounterId + WHERE loe.IsExcluded = 0 + GROUP BY f.entityid, loe.code +) t ON t.entityId = f.entityId AND t.code = loe.code +GROUP BY f.entityid, loe.code, loe.codename; diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByEntityCodePhysician.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByEntityCodePhysician.sql new file mode 100644 index 0000000..c4aa1d3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryGroupByEntityCodePhysician.sql @@ -0,0 +1,29 @@ +-- {queryListOfEncounters} + +SELECT + f.entityid, + loe.code, + f.primaryphysicianid as physicianid, + dph.fullname as physicianname, + MAX(IFNULL(t.medianvdc, 0)) as medianvdc, + MAX(IFNULL(t.medianlos, 0)) medianlos, + MAX(IFNULL(t.averagelos, 0)) averagelos, + MAX(IFNULL(t.mediancharges, 0)) as mediancharges +FROM clientdss.factpatientencountersummary f +JOIN ListOfEncounters loe ON f.EncounterId = loe.EncounterId +JOIN dss.dimphysician dph ON dph.physicianid = f.primaryphysicianid +LEFT JOIN ( + SELECT + f.entityid, + loe.code, + f.primaryphysicianid, + MEDIAN(loe.variabledirectcost) AS medianvdc, + MEDIAN(f.lengthofstay) AS medianlos, + AVG(f.lengthofstay) AS averagelos, + MEDIAN(loe.charges) AS mediancharges + FROM clientdss.factpatientencountersummary f + JOIN ListOfEncounters loe ON f.EncounterId = loe.EncounterId + WHERE loe.IsExcluded = 0 + GROUP BY f.entityid, loe.code, f.primaryphysicianid +) t ON t.entityId = f.entityId AND t.code = loe.code AND t.primaryphysicianid = f.primaryphysicianid +GROUP BY f.entityid, loe.code, f.primaryphysicianid, dph.fullname; diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryPhysicianData.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryPhysicianData.sql new file mode 100644 index 0000000..1f4dbbe --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/Builder/ExplorationQueryPhysicianData.sql @@ -0,0 +1,20 @@ +-- {queryListOfEncounters} + +SELECT DISTINCT + dph.PhysicianID, + dph.FullName AS PhysicianName, + dph.MemberGUID AS StrPhysicianGUID, + + dphs.PhysicianSpecialtyID, + dphs.Name AS PhysicianSpecialty, + dphs.MemberGUID AS StrPhysicianSpecialtyGUID, + + dphga.PhysicianGroupAffiliationID, + dphga.Name AS PhysicianGroupAffiliation, + dphga.MemberGuid AS StrPhysicianGroupAffiliationGuid + +FROM clientdss.factpatientencountersummary f +JOIN ListOfEncounters loe ON f.EncounterId = loe.EncounterId +JOIN dss.dimphysician dph ON dph.physicianid = f.primaryphysicianid +JOIN dss.DimPhysicianSpecialty dphs ON dphs.PhysicianSpecialtyID = dph.PhysicianSpecialtyID +JOIN dss.DimPhysicianGroupAffiliation dphga on dphga.PhysicianGroupAffiliationID = dph.PhysicianGroupAffiliationID; diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilySummaryDetailQuery.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilySummaryDetailQuery.sql new file mode 100644 index 0000000..9cb41b9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilySummaryDetailQuery.sql @@ -0,0 +1,222 @@ +WITH PatientEncounterSummary (encounterid, patienttypeid, dischargestatusid, aprdrgid, lengthofstay, variabledirectcost, dischargedatetime, msdrgcasetypefamilyid, aprdrgcasetypefamilyid, cptcodecasetypefamilyid, primarycptid, reportingaprdrgid, reportingdrgid, admitsourceid, admittypeid, agecohortid, entityid, genderid, icd10dxprimarydiagid, icd10pxprimaryid, insuranceplan1id, locationid, msdrgid, newbornflagid, primaryphysicianid, sourcesystemid, dischargedateid, primaryqviid) AS ( + SELECT + fpes.encounterid, + fpes.patienttypeid, + fpes.dischargestatusid, + fpes.aprdrgid, + fpes.lengthofstay, + fpes.variabledirectcost, + fpes.dischargedatetime, + fpes.msdrgcasetypefamilyid, + fpes.aprdrgcasetypefamilyid, + fpes.cptcodecasetypefamilyid, + fpes.primarycptid, + fpes.reportingaprdrgid, + fpes.reportingdrgid, + fpes.admitsourceid, + fpes.admittypeid, + fpes.agecohortid, + fpes.entityid, + fpes.genderid, + fpes.icd10dxprimarydiagid, + fpes.icd10pxprimaryid, + fpes.insuranceplan1id, + fpes.locationid, + fpes.msdrgid, + fpes.newbornflagid, + fpes.primaryphysicianid, + fpes.sourcesystemid, + fpes.dischargedateid, + fpes.primaryqviid + FROM clientDSS.FactPatientEncounterSummary fpes + WHERE fpes.dischargedatetime >= @DischargeDateStart AND fpes.dischargedatetime < @DischargeDateEnd +), + +EncounterList (encounterid, patienttypeid, dischargestatusid, aprdrgid, lengthofstay, variabledirectcost, dischargedatetime, msdrgcasetypefamilyid, aprdrgcasetypefamilyid, cptcodecasetypefamilyid, primarycptid, reportingaprdrgid, reportingdrgid) AS ( + SELECT + fpes.encounterid, + fpes.patienttypeid, + fpes.dischargestatusid, + fpes.aprdrgid, + fpes.lengthofstay, + fpes.variabledirectcost, + fpes.dischargedatetime, + fpes.msdrgcasetypefamilyid, + fpes.aprdrgcasetypefamilyid, + fpes.cptcodecasetypefamilyid, + fpes.primarycptid, + fpes.reportingaprdrgid, + fpes.reportingdrgid + FROM PatientEncounterSummary fpes + WHERE 1=1 + -- {encounterSummaryFilter} +), + +EncounterListWithCtf (encounterid, patienttypeid, dischargestatusid, aprdrgid, lengthofstay, variabledirectcost, dischargedatetime, casetypefamilyid, codeid, codeType) AS ( + SELECT + el.encounterid, + el.patienttypeid, + el.dischargestatusid, + el.aprdrgid, + el.lengthofstay, + el.variabledirectcost, + el.dischargedatetime, + (CASE + WHEN dptr.Type = 'Outpatient' THEN el.CPTCodeCaseTypeFamilyID + WHEN @CodeType = 0 THEN el.msdrgcasetypefamilyid + ELSE el.aprdrgcasetypefamilyid + END) AS casetypefamilyid, + (CASE + WHEN dptr.Type = 'Outpatient' THEN el.primarycptid + WHEN @CodeType = 1 THEN el.reportingaprdrgid + ELSE el.reportingdrgid + END) AS codeid, + (CASE + WHEN dptr.Type = 'Outpatient' THEN 3 + ELSE @CodeType + END) AS codeType + FROM EncounterList el + JOIN fw.DimPatientType dpt ON dpt.PatientTypeID = el.PatientTypeID + JOIN fw.DimPatientTypeRollup dptr ON dptr.PatientTypeRollupID = dpt.PatientTypeRollupID + WHERE casetypefamilyid = @CaseTypeFamilyId +), + +EncounterCosts (encounterid, charges, variabledirectcost, costdriver) AS ( + SELECT + el.encounterid, + SUM(c.charge) charges, + SUM(c.variabledirectcost) AS variabledirectcost, + cc.costdriver + FROM EncounterListWithCtf el + JOIN cci.encounterchargecodecosts c on el.encounterid = c.encounterid + JOIN fw.dimchargecode cc on cc.chargecodeid = c.chargecodeid + GROUP BY el.encounterid, cc.costdriver +), + +EncounterListWithCharges (encounterid, charges, variabledirectcost, isexpired, rom, soi, costdriver, casetypefamilyid, codeid, lengthofstay, dischargedatetime, codeType) AS ( + SELECT + el.encounterid, + ec.charges, + ec.variabledirectcost, + CASE WHEN ds.dischargestatuscode IN ('20', '40', '41', '42') THEN 1 ELSE 0 END AS isexpired, + COALESCE(apr.aprrom, '0') AS rom, + COALESCE(apr.aprsoi, '0') AS soi, + ec.costdriver, + el.casetypefamilyid, + el.codeid, + el.lengthofstay, + el.dischargedatetime, + el.codeType + FROM EncounterListWithCtf el + JOIN EncounterCosts ec on el.encounterid = ec.encounterid + JOIN clientdss.dimdischargestatus ds ON el.dischargestatusid = ds.dischargestatusid + LEFT JOIN dss.dimaprdrg apr ON el.aprdrgid = apr.aprdrgid +), + +EncounterUniqueList (EncounterID, CaseTypeFamilyID) AS ( + SELECT + EncounterId, + (CASE + WHEN dptr.Type = 'Outpatient' THEN pes.CPTCodeCaseTypeFamilyID + WHEN @CodeType = 0 THEN pes.msdrgcasetypefamilyid + ELSE pes.aprdrgcasetypefamilyid + END) AS casetypefamilyid + FROM PatientEncounterSummary pes + JOIN fw.DimPatientType dpt ON dpt.PatientTypeID = pes.PatientTypeID + JOIN fw.DimPatientTypeRollup dptr ON dptr.PatientTypeRollupID = dpt.PatientTypeRollupID +), + +ListOfEncounters (EncounterID, Charges, VariableDirectCost, IsExpired, ROM, SOI, CostDriver, CaseTypeFamilyID, LengthOfStay, DischargeDateTime) AS ( + SELECT + el.EncounterID, + el.Charges, + el.VariableDirectCost, + el.IsExpired, + el.ROM, + el.SOI, + el.CostDriver, + el.CaseTypeFamilyID, + el.lengthofstay, + el.dischargedatetime + FROM EncounterListWithCharges el + LEFT JOIN dss.dimmsdrg dmsd ON dmsd.msdrgid = el.codeid AND el.codeType = 0 + LEFT JOIN dss.dimmsdrgrollup dmsdroll ON dmsdroll.msdrgrollupid = dmsd.msdrgrollupid + -- {queryFilter} +), + +ctfGroupCalcs (casetypefamilyid, medianvdc, medianlos, averagelos, twentyfivepercentilevdc, seventyfivepercentilevdc, twentyfivepercentilelos, seventyfivepercentilelos) AS ( + SELECT + eul.CaseTypeFamilyID, + MEDIAN(el.variabledirectcost) AS medianvdc, + MEDIAN(el.lengthofstay) AS medianlos, + AVG(el.lengthofstay) AS averagelos, + PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY el.variabledirectcost) AS twentyfivepercentilevdc, + PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY el.variabledirectcost) AS seventyfivepercentilevdc, + PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY el.lengthofstay) AS twentyfivepercentilelos, + PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY el.lengthofstay) AS seventyfivepercentilelos + FROM EncounterList el + JOIN EncounterUniqueList eul ON el.EncounterID = eul.EncounterID + GROUP BY eul.casetypefamilyid +), + +ctfCostDriverGroupCalcs (casetypefamilyid, costdriver, medianvdc) AS ( + SELECT + loe.casetypefamilyid, + loe.costdriver, + MEDIAN(loe.variabledirectcost) AS medianvdc + FROM ListOfEncounters loe + GROUP BY casetypefamilyid, costdriver +), + +madsubcalc (encounterid, casetypefamilyid, vdcdifference, losdifference) AS ( + SELECT + el.encounterid, + g.casetypefamilyid, + ABS(el.variabledirectcost - g.medianvdc) AS vdcdifference, + ABS(el.lengthofstay - g.medianlos) AS losdifference + FROM EncounterList el + JOIN EncounterUniqueList eul ON eul.EncounterID = el.EncounterID + JOIN ctfGroupCalcs g ON g.casetypefamilyid = eul.casetypefamilyid +), + +mads (casetypefamilyid, madvdc, madlos) AS ( + SELECT + casetypefamilyid, + MEDIAN(vdcdifference) as madvdc, + MEDIAN(losdifference) as madlos + FROM madsubcalc + GROUP BY casetypefamilyid +) + +SELECT + el.encounterid, + el.dischargedatetime, + loe.casetypefamilyid, + ctf.name AS casetypefamilyname, + loe.isexpired, + loe.variabledirectcost, + el.variabledirectcost vdc, + el.lengthofstay, + cg.medianvdc as medianvariabledirectcost, + loe.variabledirectcost - cg.medianvdc AS identifiedvariation, + g.medianvdc, + g.medianlos as medianlengthofstay, + g.averagelos as averagelengthofstay, + g.twentyfivepercentilevdc, + g.seventyfivepercentilevdc, + g.seventyfivepercentilevdc - g.twentyfivepercentilevdc as iqrvdc, + g.twentyfivepercentilelos, + g.seventyfivepercentilelos, + g.seventyfivepercentilelos - g.twentyfivepercentilelos as iqrlos, + m.madvdc * 1.4826 as madvdc, + m.madlos * 1.4826 as madlos, + loe.rom, + loe.soi, + loe.charges, + loe.costdriver +FROM EncounterList el + JOIN ListOfEncounters loe ON el.EncounterId = loe.EncounterId + JOIN dss.casetypefamily ctf ON ctf.casetypefamilyid = loe.casetypefamilyid + JOIN ctfGroupCalcs g ON g.casetypefamilyid = loe.casetypefamilyid + JOIN ctfCostDriverGroupCalcs cg ON cg.casetypefamilyid = loe.casetypefamilyid AND cg.costdriver = loe.costdriver + JOIN mads m on m.casetypefamilyid = loe.casetypefamilyid diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilySummaryEncounterQuery.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilySummaryEncounterQuery.sql new file mode 100644 index 0000000..40ca942 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilySummaryEncounterQuery.sql @@ -0,0 +1,150 @@ +WITH PatientEncounterSummary (encounterid, patienttypeid, dischargestatusid, aprdrgid, lengthofstay, variabledirectcost, dischargedatetime, msdrgcasetypefamilyid, aprdrgcasetypefamilyid, cptcodecasetypefamilyid, primarycptid, reportingaprdrgid, reportingdrgid, admitsourceid, admittypeid, agecohortid, entityid, genderid, icd10dxprimarydiagid, icd10pxprimaryid, insuranceplan1id, locationid, msdrgid, newbornflagid, primaryphysicianid, sourcesystemid, dischargedateid, primaryqviid) AS ( + SELECT + fpes.encounterid, + fpes.patienttypeid, + fpes.dischargestatusid, + fpes.aprdrgid, + fpes.lengthofstay, + fpes.variabledirectcost, + fpes.dischargedatetime, + fpes.msdrgcasetypefamilyid, + fpes.aprdrgcasetypefamilyid, + fpes.cptcodecasetypefamilyid, + fpes.primarycptid, + fpes.reportingaprdrgid, + fpes.reportingdrgid, + fpes.admitsourceid, + fpes.admittypeid, + fpes.agecohortid, + fpes.entityid, + fpes.genderid, + fpes.icd10dxprimarydiagid, + fpes.icd10pxprimaryid, + fpes.insuranceplan1id, + fpes.locationid, + fpes.msdrgid, + fpes.newbornflagid, + fpes.primaryphysicianid, + fpes.sourcesystemid, + fpes.dischargedateid, + fpes.primaryqviid + FROM clientDSS.FactPatientEncounterSummary fpes + WHERE fpes.dischargedatetime >= @DischargeDateStart AND fpes.dischargedatetime < @DischargeDateEnd +), + +EncounterList (encounterid, patienttypeid, dischargestatusid, aprdrgid, lengthofstay, variabledirectcost, dischargedatetime, msdrgcasetypefamilyid, aprdrgcasetypefamilyid, cptcodecasetypefamilyid, primarycptid, reportingaprdrgid, reportingdrgid) AS ( + SELECT + fpes.encounterid, + fpes.patienttypeid, + fpes.dischargestatusid, + fpes.aprdrgid, + fpes.lengthofstay, + fpes.variabledirectcost, + fpes.dischargedatetime, + fpes.msdrgcasetypefamilyid, + fpes.aprdrgcasetypefamilyid, + fpes.cptcodecasetypefamilyid, + fpes.primarycptid, + fpes.reportingaprdrgid, + fpes.reportingdrgid + FROM PatientEncounterSummary fpes + WHERE 1=1 + -- {encounterSummaryFilter} +), + +EncounterListWithCtf (encounterid, patienttypeid, dischargestatusid, aprdrgid, lengthofstay, variabledirectcost, dischargedatetime, casetypefamilyid, codeid, codeType) AS ( + SELECT + el.encounterid, + el.patienttypeid, + el.dischargestatusid, + el.aprdrgid, + el.lengthofstay, + el.variabledirectcost, + el.dischargedatetime, + (CASE + WHEN dptr.Type = 'Outpatient' THEN el.CPTCodeCaseTypeFamilyID + WHEN @CodeType = 0 THEN el.msdrgcasetypefamilyid + ELSE el.aprdrgcasetypefamilyid + END) AS casetypefamilyid, + (CASE + WHEN dptr.Type = 'Outpatient' THEN el.primarycptid + WHEN @CodeType = 1 THEN el.reportingaprdrgid + ELSE el.reportingdrgid + END) AS codeid, + (CASE + WHEN dptr.Type = 'Outpatient' THEN 3 + ELSE @CodeType + END) AS codeType + FROM EncounterList el + JOIN fw.DimPatientType dpt ON dpt.PatientTypeID = el.PatientTypeID + JOIN fw.DimPatientTypeRollup dptr ON dptr.PatientTypeRollupID = dpt.PatientTypeRollupID + WHERE casetypefamilyid = @CaseTypeFamilyId +), + +EncounterCosts (encounterid, charges, variabledirectcost, costdriver) AS ( + SELECT + el.encounterid, + SUM(c.charge) charges, + SUM(c.variabledirectcost) AS variabledirectcost, + cc.costdriver + FROM EncounterListWithCtf el + JOIN cci.encounterchargecodecosts c on el.encounterid = c.encounterid + JOIN fw.dimchargecode cc on cc.chargecodeid = c.chargecodeid + GROUP BY el.encounterid, cc.costdriver +), + +EncounterListWithCharges (encounterid, charges, variabledirectcost, isexpired, rom, soi, costdriver, casetypefamilyid, codeid, lengthofstay, dischargedatetime, codeType) AS ( + SELECT + el.encounterid, + ec.charges, + ec.variabledirectcost, + CASE WHEN ds.dischargestatuscode IN ('20', '40', '41', '42') THEN 1 ELSE 0 END AS isexpired, + COALESCE(apr.aprrom, '0') AS rom, + COALESCE(apr.aprsoi, '0') AS soi, + ec.costdriver, + el.casetypefamilyid, + el.codeid, + el.lengthofstay, + el.dischargedatetime, + el.codeType + FROM EncounterListWithCtf el + JOIN EncounterCosts ec on el.encounterid = ec.encounterid + JOIN clientdss.dimdischargestatus ds ON el.dischargestatusid = ds.dischargestatusid + LEFT JOIN dss.dimaprdrg apr ON el.aprdrgid = apr.aprdrgid +), + +EncounterUniqueList (EncounterID, CaseTypeFamilyID) AS ( + SELECT + EncounterId, + (CASE + WHEN dptr.Type = 'Outpatient' THEN pes.CPTCodeCaseTypeFamilyID + WHEN @CodeType = 0 THEN pes.msdrgcasetypefamilyid + ELSE pes.aprdrgcasetypefamilyid + END) AS casetypefamilyid + FROM PatientEncounterSummary pes + JOIN fw.DimPatientType dpt ON dpt.PatientTypeID = pes.PatientTypeID + JOIN fw.DimPatientTypeRollup dptr ON dptr.PatientTypeRollupID = dpt.PatientTypeRollupID +), + +ListOfEncounters (EncounterID, Charges, VariableDirectCost, IsExpired, ROM, SOI, CostDriver, CaseTypeFamilyID, LengthOfStay, DischargeDateTime) AS ( + SELECT + el.EncounterID, + el.Charges, + el.VariableDirectCost, + el.IsExpired, + el.ROM, + el.SOI, + el.CostDriver, + el.CaseTypeFamilyID, + el.lengthofstay, + el.dischargedatetime + FROM EncounterListWithCharges el + LEFT JOIN dss.dimmsdrg dmsd ON dmsd.msdrgid = el.codeid AND el.codeType = 0 + LEFT JOIN dss.dimmsdrgrollup dmsdroll ON dmsdroll.msdrgrollupid = dmsd.msdrgrollupid + -- {queryFilter} +) + +SELECT + DISTINCT el.encounterid AS "FilterEncounterId" +FROM EncounterList el + JOIN ListOfEncounters loe ON el.EncounterId = loe.EncounterId diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilySummaryQuery.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilySummaryQuery.sql new file mode 100644 index 0000000..5378027 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilySummaryQuery.sql @@ -0,0 +1,346 @@ +WITH PatientEncounterSummary (encounterid, patienttypeid, dischargestatusid, aprdrgid, lengthofstay, variabledirectcost, dischargedatetime, msdrgcasetypefamilyid, aprdrgcasetypefamilyid, cptcodecasetypefamilyid, primarycptid, reportingaprdrgid, reportingdrgid, admitsourceid, admittypeid, agecohortid, entityid, genderid, icd10dxprimarydiagid, icd10pxprimaryid, insuranceplan1id, locationid, msdrgid, newbornflagid, primaryphysicianid, sourcesystemid, dischargedateid, primaryqviid) AS ( + SELECT + fpes.encounterid, + fpes.patienttypeid, + fpes.dischargestatusid, + fpes.aprdrgid, + fpes.lengthofstay, + fpes.variabledirectcost, + fpes.dischargedatetime, + fpes.msdrgcasetypefamilyid, + fpes.aprdrgcasetypefamilyid, + fpes.cptcodecasetypefamilyid, + fpes.primarycptid, + fpes.reportingaprdrgid, + fpes.reportingdrgid, + fpes.admitsourceid, + fpes.admittypeid, + fpes.agecohortid, + fpes.entityid, + fpes.genderid, + fpes.icd10dxprimarydiagid, + fpes.icd10pxprimaryid, + fpes.insuranceplan1id, + fpes.locationid, + fpes.msdrgid, + fpes.newbornflagid, + fpes.primaryphysicianid, + fpes.sourcesystemid, + fpes.dischargedateid, + fpes.primaryqviid + FROM clientDSS.FactPatientEncounterSummary fpes + WHERE fpes.dischargedatetime >= @DischargeDateStart AND fpes.dischargedatetime < @DischargeDateEnd +), + +EncounterList (encounterid, patienttypeid, dischargestatusid, aprdrgid, lengthofstay, variabledirectcost, dischargedatetime, msdrgcasetypefamilyid, aprdrgcasetypefamilyid, cptcodecasetypefamilyid, primarycptid, reportingaprdrgid, reportingdrgid) AS ( + SELECT + fpes.encounterid, + fpes.patienttypeid, + fpes.dischargestatusid, + fpes.aprdrgid, + fpes.lengthofstay, + fpes.variabledirectcost, + fpes.dischargedatetime, + fpes.msdrgcasetypefamilyid, + fpes.aprdrgcasetypefamilyid, + fpes.cptcodecasetypefamilyid, + fpes.primarycptid, + fpes.reportingaprdrgid, + fpes.reportingdrgid + FROM PatientEncounterSummary fpes + WHERE 1=1 + -- {encounterSummaryFilter} +), + +EncounterListWithCtf (encounterid, patienttypeid, dischargestatusid, aprdrgid, lengthofstay, variabledirectcost, dischargedatetime, casetypefamilyid, codeid, codeType) AS ( + SELECT + el.encounterid, + el.patienttypeid, + el.dischargestatusid, + el.aprdrgid, + el.lengthofstay, + el.variabledirectcost, + el.dischargedatetime, + (CASE + WHEN dptr.Type = 'Outpatient' THEN el.CPTCodeCaseTypeFamilyID + WHEN @CodeType = 0 THEN el.msdrgcasetypefamilyid + ELSE el.aprdrgcasetypefamilyid + END) AS casetypefamilyid, + (CASE + WHEN dptr.Type = 'Outpatient' THEN el.primarycptid + WHEN @CodeType = 1 THEN el.reportingaprdrgid + ELSE el.reportingdrgid + END) AS codeid, + (CASE + WHEN dptr.Type = 'Outpatient' THEN 3 -- codeType 3 = outpatient (CPT-based) classification + ELSE @CodeType + END) AS codeType + FROM EncounterList el + JOIN fw.DimPatientType dpt ON dpt.PatientTypeID = el.PatientTypeID + JOIN fw.DimPatientTypeRollup dptr ON dptr.PatientTypeRollupID = dpt.PatientTypeRollupID + WHERE casetypefamilyid <> 0 +), + +EncounterCosts (encounterid, charges, variabledirectcost, costdriver) AS ( + SELECT + el.encounterid, + SUM(c.charge) charges, + SUM(c.variabledirectcost) AS variabledirectcost, + cc.costdriver + FROM EncounterListWithCtf el + JOIN cci.encounterchargecodecosts c on el.encounterid = c.encounterid + JOIN fw.dimchargecode cc on cc.chargecodeid = c.chargecodeid + GROUP BY el.encounterid, cc.costdriver +), + +EncounterListWithCharges (encounterid, charges, variabledirectcost, isexpired, rom, soi, costdriver, casetypefamilyid, codeid, lengthofstay, dischargedatetime, codeType) AS ( + SELECT + el.encounterid, + ec.charges, + ec.variabledirectcost, + CASE WHEN ds.dischargestatuscode IN ('20', '40', '41', '42') THEN 1 ELSE 0 END AS isexpired, + COALESCE(apr.aprrom, '0') AS rom, + COALESCE(apr.aprsoi, '0') AS soi, + ec.costdriver, + el.casetypefamilyid, + el.codeid, + el.lengthofstay, + el.dischargedatetime, + el.codeType + FROM EncounterListWithCtf el + JOIN EncounterCosts ec on el.encounterid = ec.encounterid + JOIN clientdss.dimdischargestatus ds ON el.dischargestatusid = ds.dischargestatusid + LEFT JOIN dss.dimaprdrg apr ON el.aprdrgid = apr.aprdrgid +), + +EncounterUniqueList (EncounterID, CaseTypeFamilyID) AS ( + SELECT + EncounterId, + (CASE + WHEN dptr.Type = 'Outpatient' THEN pes.CPTCodeCaseTypeFamilyID + WHEN @CodeType = 0 THEN pes.msdrgcasetypefamilyid + ELSE pes.aprdrgcasetypefamilyid + END) AS casetypefamilyid + FROM PatientEncounterSummary pes + JOIN fw.DimPatientType dpt ON dpt.PatientTypeID = pes.PatientTypeID + JOIN fw.DimPatientTypeRollup dptr ON dptr.PatientTypeRollupID = dpt.PatientTypeRollupID +), + +ListOfEncounters (EncounterID, Charges, VariableDirectCost, IsExpired, ROM, SOI, CostDriver, CaseTypeFamilyID, lengthofstay, dischargedatetime) AS ( + SELECT + el.EncounterID, + el.Charges, + el.VariableDirectCost, + el.IsExpired, + el.ROM, + el.SOI, + el.CostDriver, + el.CaseTypeFamilyID, + el.lengthofstay, + el.dischargedatetime + FROM EncounterListWithCharges el + LEFT JOIN dss.dimmsdrg dmsd ON dmsd.msdrgid = el.codeid and el.codeType = 0 + LEFT JOIN dss.dimmsdrgrollup dmsdroll ON dmsdroll.msdrgrollupid = dmsd.msdrgrollupid + -- {queryFilter} +), + +ctfGroupCalcs (casetypefamilyid, medianvdc, medianlos, averageLos, twentyfivepercentilevdc, seventyfivepercentilevdc, twentyfivepercentilelos, seventyfivepercentilelos) AS ( + SELECT + eul.CaseTypeFamilyID, + MEDIAN(el.variabledirectcost) AS medianvdc, + MEDIAN(el.lengthofstay) AS medianlos, + AVG(el.lengthofstay) as averageLos, + PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY el.variabledirectcost) AS twentyfivepercentilevdc, + PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY el.variabledirectcost) AS seventyfivepercentilevdc, + PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY el.lengthofstay) AS twentyfivepercentilelos, + PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY el.lengthofstay) AS seventyfivepercentilelos + FROM EncounterList el + JOIN EncounterUniqueList eul ON el.EncounterID = eul.EncounterID + GROUP BY eul.casetypefamilyid +), + +ctfCostDriverGroupCalcs (casetypefamilyid, costdriver, medianvdc) AS ( + SELECT + loe.casetypefamilyid, + loe.costdriver, + MEDIAN(loe.variabledirectcost) AS medianvdc + FROM ListOfEncounters loe + GROUP BY casetypefamilyid, costdriver +), + +madsubcalc (encounterid, casetypefamilyid, vdcdifference, losdifference) AS ( + SELECT + el.encounterid, + g.casetypefamilyid, + ABS(el.variabledirectcost - g.medianvdc) AS vdcdifference, + ABS(el.lengthofstay - g.medianlos) AS losdifference + FROM EncounterList el + JOIN EncounterUniqueList eul ON eul.EncounterID = el.EncounterID + JOIN ctfGroupCalcs g ON g.casetypefamilyid = eul.casetypefamilyid +), + +mads (casetypefamilyid, madvdc, madlos) AS ( + SELECT + casetypefamilyid, + MEDIAN(vdcdifference) as madvdc, + MEDIAN(losdifference) as madlos + FROM madsubcalc + GROUP BY casetypefamilyid +), + +ctfSummaryDetailData (encounterid, dischargedatetime, casetypefamilyid, casetypefamilyname, isexpired, variabledirectcost, vdc, lengthofstay, identifiedvariation, medianvdc, medianlengthofstay, averagelengthofstay, twentyfivepercentilevdc, seventyfivepercentilevdc, iqrvdc, twentyfivepercentilelos, seventyfivepercentilelos, iqrlos, madvdc, madlos, rom, soi, charges, costdriver) AS ( + SELECT + el.encounterid, + el.dischargedatetime, + loe.casetypefamilyid, + ctf.name AS casetypefamilyname, + loe.isexpired, + loe.variabledirectcost, + el.variabledirectcost vdc, + el.lengthofstay, + --rounding to block extremely small values from being marked as identified variation later on + ROUND(loe.variabledirectcost - cg.medianvdc, 5) AS identifiedvariation, + g.medianvdc, + g.medianlos as medianlengthofstay, + g.averageLos as averagelengthofstay, + g.twentyfivepercentilevdc, + g.seventyfivepercentilevdc, + g.seventyfivepercentilevdc - g.twentyfivepercentilevdc as iqrvdc, + g.twentyfivepercentilelos, + g.seventyfivepercentilelos, + g.seventyfivepercentilelos - g.twentyfivepercentilelos as iqrlos, + m.madvdc * 1.4826 as madvdc, + m.madlos * 1.4826 as madlos, + loe.rom, + loe.soi, + loe.charges, + loe.costdriver + FROM EncounterList el + JOIN ListOfEncounters loe on loe.encounterid = el.encounterid + JOIN dss.casetypefamily ctf ON ctf.casetypefamilyid = loe.casetypefamilyid + JOIN ctfGroupCalcs g ON g.casetypefamilyid = loe.casetypefamilyid + JOIN ctfCostDriverGroupCalcs cg ON cg.casetypefamilyid = loe.casetypefamilyid AND cg.costdriver = loe.costdriver + JOIN mads m on m.casetypefamilyid = loe.casetypefamilyid +), +includedEncounterInfos (encounterid, casetypefamilyid, costdriver, identifiedvariation, lengthofstay, variableDirectCost, isExpired ) AS ( + SELECT + encounterid, + casetypefamilyid, + costdriver, + identifiedvariation, + lengthofstay, + variableDirectCost, + isExpired + FROM ctfSummaryDetailData + WHERE + ( + -- LOS exclusion + @LosExclusionType = 'None' + OR ( + lengthofstay + <= + CASE + WHEN @LosExclusionType = 'Mad' THEN (madlos * @LosExclusionValue) + medianlengthofstay + WHEN @LosExclusionType = 'Iqr' THEN (iqrlos * @LosExclusionValue) + seventyfivepercentilelos + ELSE NULL + END + ) + ) + AND + ( + -- VDC exclusion + @CostExclusionType = 'None' + OR ( + vdc + <= + CASE + WHEN @CostExclusionType = 'Mad' THEN (madvdc * @CostExclusionValue) + medianvdc + WHEN @CostExclusionType = 'Iqr' THEN (iqrvdc * @CostExclusionValue) + seventyfivepercentilevdc + ELSE NULL + END + ) + ) +), +includedEncounterCostAgg AS ( + SELECT + encounterid, + casetypefamilyid, + SUM(variabledirectcost) as totalVdc + FROM includedEncounterInfos + GROUP BY encounterid, casetypefamilyid +), +includedMedianCost AS ( + SELECT + casetypefamilyid, + MEDIAN(totalVdc) AS includedMedianCost + FROM includedEncounterCostAgg + GROUP BY casetypefamilyid +), +includedEncounterValues AS ( + SELECT + DISTINCT(encounterid), + casetypefamilyid, + isexpired, + lengthofstay + FROM includedEncounterInfos +), +includedCalcs AS ( + SELECT + casetypefamilyid, + AVG(isexpired) AS mortalityrate, + MEDIAN(lengthofstay) AS lengthofstay, + AVG(lengthofstay) as averagelengthofstay + FROM includedEncounterValues + GROUP BY casetypefamilyid +), +distinctIncludedVariationEncounters AS ( + SELECT + COUNT(DISTINCT detail.encounterid) AS includedVariationEncounters, + detail.casetypefamilyid + FROM includedEncounterInfos detail + WHERE detail.identifiedVariation > 0 + GROUP BY detail.casetypefamilyid +), +ctfSummary (casetypefamilyid, casetypefamilyname, +totalEncounters, totalVariationEncounters, identifiedvariation, +includedEncounterCount, mortalityRate, medianLos, averagelengthofstay, medianCost, includedEncountersBelowMedian, includedEncountersAboveMedian, includedVariation, +excludedEncountersAboveMedian, excludedVariation +) AS ( + SELECT + detail.casetypefamilyid, + detail.casetypefamilyname, + -- entire population + COUNT(DISTINCT detail.encounterid) totalEncounters, + COUNT(DISTINCT CASE WHEN detail.identifiedVariation > 0 THEN detail.encounterid ELSE NULL END) AS totalVariationEncounters, + SUM(CASE WHEN detail.identifiedvariation > 0 THEN detail.identifiedvariation ELSE 0 END) AS identifiedvariation, + + -- -- refined population + COUNT(DISTINCT included.encounterid) AS includedEncounterCount, + ic.mortalityrate AS mortalityrate, + ic.lengthofstay AS medianLos, + ic.averagelengthofstay as averagelengthofstay, + ANY_VALUE(mc.includedMedianCost) AS medianCost, + COUNT(DISTINCT included.encounterid) - ZEROIFNULL(divc.includedVariationEncounters) AS includedEncountersBelowMedian, + ZEROIFNULL(divc.includedVariationEncounters) AS includedEncountersAboveMedian, + SUM(CASE WHEN included.identifiedvariation > 0 THEN included.identifiedvariation ELSE 0 END) AS includedVariation, + + -- excluded population + COUNT(DISTINCT CASE WHEN included.encounterid IS NULL THEN detail.encounterid ELSE NULL END) AS excludedEncountersAboveMedian, + SUM(CASE WHEN included.encounterid IS NULL AND detail.identifiedvariation > 0 THEN detail.identifiedvariation ELSE 0 END) AS excludedVariation + FROM ctfSummaryDetailData detail + LEFT JOIN includedEncounterInfos included ON included.encounterid = detail.encounterid + AND included.caseTypeFamilyId = detail.caseTypeFamilyId + AND included.costdriver = detail.costdriver + LEFT JOIN includedMedianCost mc ON mc.casetypefamilyid = detail.casetypefamilyid + LEFT JOIN includedCalcs ic ON ic.casetypefamilyid = detail.casetypefamilyid + LEFT JOIN distinctIncludedVariationEncounters divc ON divc.casetypefamilyid = detail.casetypefamilyid + GROUP BY detail.casetypefamilyid, detail.casetypefamilyname, divc.includedVariationEncounters, ic.mortalityrate, ic.lengthofstay, ic.averagelengthofstay +) +SELECT +casetypefamilyid, +casetypefamilyname, +totalEncounters, totalVariationEncounters, identifiedvariation, +includedEncounterCount, mortalityRate, medianLos, averagelengthofstay, medianCost, includedEncountersBelowMedian, includedEncountersAboveMedian, includedVariation, +excludedEncountersAboveMedian, excludedVariation +FROM ctfsummary +ORDER BY casetypefamilyname; diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilyTrimQuery.sql b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilyTrimQuery.sql new file mode 100644 index 0000000..c1aa5b9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQueries/CaseTypeFamilyTrimQuery.sql @@ -0,0 +1,66 @@ +WITH EncounterList (EncounterID, Charges, IsExpired, ROM, SOI) AS ( + SELECT + fpes.EncounterID, + SUM(c.charge) Charges, + CASE WHEN ds.dischargestatuscode in ('20', '40', '41', '42') THEN 1 ELSE 0 END AS isexpired, + COALESCE(apr.aprrom, '0') AS rom, + COALESCE(apr.aprsoi, '0') AS soi + FROM clientDSS.FactPatientEncounterSummary fpes + JOIN cci.encounterchargecodecosts c on fpes.encounterid = c.encounterid + JOIN clientdss.dimdischargestatus ds on fpes.dischargestatusid = ds.dischargestatusid + LEFT JOIN dss.dimaprdrg apr ON fpes.apdrgid = apr.aprdrgid + WHERE fpes.dischargedatetime >= @DischargeDateStart AND fpes.dischargedatetime < @DischargeDateEnd + AND COALESCE(NULLIF(fpes.msdrgcasetypefamilyid, 0), NULLIF(fpes.aprdrgcasetypefamilyid, 0), NULLIF(fpes.cptcodecasetypefamilyid, 0)) = @CaseTypeFamilyId + GROUP BY fpes.EncounterId, isExpired, rom, soi +), + +EncounterUniqueList (EncounterID) AS ( + SELECT DISTINCT EncounterID + FROM EncounterList +), + +groupcalcs (casetypefamilyid, medianvdc, medianlos, twentyfivepercentilevdc, twentyfivepercentilelos, seventyfivepercentilevdc, seventyfivepercentilelos) AS ( + SELECT + COALESCE(NULLIF(f.msdrgcasetypefamilyid, 0), NULLIF(f.aprdrgcasetypefamilyid, 0), NULLIF(f.cptcodecasetypefamilyid, 0)) AS casetypefamilyid, + MEDIAN(f.variabledirectcost) AS medianvdc, + MEDIAN(f.lengthofstay) AS medianlos, + percentile_cont(0.25) within group (order by f.variabledirectcost) as twentyfivepercentilevdc, + percentile_cont(0.25) within group (order by f.lengthofstay) as twentyfivepercentilelos, + percentile_cont(0.75) within group (order by f.variabledirectcost) as seventyfivepercentilevdc, + percentile_cont(0.75) within group (order by f.lengthofstay) as seventyfivepercentilelos + FROM clientdss.factpatientencountersummary f + JOIN EncounterUniqueList el ON f.encounterId = el.encounterId + GROUP BY casetypefamilyid + ), + +madsubcalc (casetypefamilyid, vdcdifference, losdifference) as ( + SELECT + COALESCE(NULLIF(f.msdrgcasetypefamilyid, 0), NULLIF(f.aprdrgcasetypefamilyid, 0), NULLIF(f.cptcodecasetypefamilyid, 0)) AS casetypefamilyid, + ABS(f.variabledirectcost - g.medianvdc) as vdcdifference, + ABS(f.lengthofstay - g.medianlos) as losdifference + FROM clientdss.factpatientencountersummary f + JOIN EncounterUniqueList el ON f.encounterId = el.encounterId + JOIN groupcalcs as g on g.casetypefamilyid = casetypefamilyid +), + +mads (casetypefamilyid, madvdc, madlos) as ( + SELECT + casetypefamilyid, + MEDIAN(vdcdifference) as madvdc, + MEDIAN(losdifference) as madlos + FROM madsubcalc m + GROUP BY m.casetypefamilyid +) + +SELECT + m.casetypefamilyid, + m.madvdc * 1.4826 as madvdc, + m.madlos * 1.4826 as madlos, + g.medianvdc, + g.medianlos, + g.seventyfivepercentilevdc, + g.seventyfivepercentilevdc - g.twentyfivepercentilevdc as iqrvdc, + g.seventyfivepercentilelos, + g.seventyfivepercentilelos - g.twentyfivepercentilelos as iqrlos +FROM mads m +JOIN groupcalcs g on g.casetypefamilyid = m.casetypefamilyid \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQuery.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQuery.cs new file mode 100644 index 0000000..bd72c7b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/ExplorationServiceQuery.cs @@ -0,0 +1,102 @@ +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public static class ExplorationServiceQuery + { + private static readonly Func SnowflakeSyntax = (sql) + => Regex.Replace(sql.Replace("@", ":"), @"IN \((:[^)]*)\)", "IN $1", RegexOptions.IgnoreCase | RegexOptions.Multiline); + + private static readonly Func ApplyQueryFilters = (queryFilters, sql) + => sql.Replace("-- {queryFilter}", queryFilters); + + private static readonly Func ApplyEncounterSummaryFilters = (encounterSummaryFilters, sql) + => sql.Replace("-- {encounterSummaryFilter}", encounterSummaryFilters); + + private static readonly Func ApplyQueryTrim = (queryTrim, sql) + => sql.Replace("-- {queryTrim}", queryTrim); + + private static readonly Func ApplyQueryListOfEncounters = (queryListOfEncounters, sql) + => sql.Replace("-- {queryListOfEncounters}", queryListOfEncounters); + + private static readonly Func ApplyQuerySelect = (querySelect, sql) + => sql.Replace("-- {querySelect}", querySelect); + + private static readonly Func ApplyQueryAdditional = (queryAdditionalColumns, queryAdditionalJoins, sql) + => sql.Replace("-- {queryAdditionalColumns}", queryAdditionalColumns).Replace("-- {queryAdditionalJoins}", queryAdditionalJoins); + + /// + /// Returns the CaseTypeFamily Summary Query string + /// + /// + /// + /// Likely not enough logic to constitute its own function, but I imagine as we build this out + /// there will be more parameters to pass into here, or maybe a place where we would want to reuse this logic + /// + public static string BuildCaseTypeFamilySummaryQuery(string queryFilters, string encounterSummaryFilters) + { + var assembly = Assembly.GetExecutingAssembly(); + return SnowflakeSyntax(ApplyQueryFilters(queryFilters, ApplyEncounterSummaryFilters(encounterSummaryFilters, assembly.ReadResource("CaseTypeFamilySummaryQuery.sql")))); + } + + public static string BuildCaseTypeFamilySummaryDetailQuery(string queryFilters, string encounterSummaryFilters) + { + var assembly = Assembly.GetExecutingAssembly(); + return SnowflakeSyntax(ApplyQueryFilters(queryFilters, ApplyEncounterSummaryFilters(encounterSummaryFilters, assembly.ReadResource("CaseTypeFamilySummaryDetailQuery.sql")))); + } + + public static string BuildCaseTypeFamilySummaryEncounterQuery(string queryFilters, string encounterSummaryFilters) + { + var assembly = Assembly.GetExecutingAssembly(); + return SnowflakeSyntax(ApplyQueryFilters(queryFilters, ApplyEncounterSummaryFilters(encounterSummaryFilters, assembly.ReadResource("CaseTypeFamilySummaryEncounterQuery.sql")))); + } + + public static string CaseTypeFamilyTrimQuery() + { + var assembly = Assembly.GetExecutingAssembly(); + return SnowflakeSyntax(assembly.ReadResource("CaseTypeFamilyTrimQuery.sql")); + } + + public static (string, byte) CTFEntityCodePhysicianQuery(string queryFilter, string encounterSummaryFilters, string queryJoin, string queryTrim) + { + var sbQuerySelect = new StringBuilder(); + var assembly = Assembly.GetExecutingAssembly(); + sbQuerySelect.Append(assembly.ReadResource("ExplorationQueryGroupByCtf.sql")); + sbQuerySelect.Append(assembly.ReadResource("ExplorationQueryGroupByEntity.sql")); + sbQuerySelect.Append(assembly.ReadResource("ExplorationQueryGroupByEntityCode.sql")); + sbQuerySelect.Append(assembly.ReadResource("ExplorationQueryGroupByEntityCodePhysician.sql")); + sbQuerySelect.Append(assembly.ReadResource("ExplorationQueryEncountersData.sql")); + + var query = + ApplyQueryTrim(queryTrim, + ApplyQueryFilters(queryFilter, + ApplyEncounterSummaryFilters(encounterSummaryFilters, + ApplyQueryAdditional(", loc.code, loc.codename", queryJoin + $"JOIN ({assembly.ReadResource("ExplorationQueryCodes.sql")}) loc ON loc.ctftype = el.ctftype AND loc.codeid = el.codeid", + ApplyQueryListOfEncounters(assembly.ReadResource("ExplorationQueryBase.sql"), + ApplyQuerySelect(sbQuerySelect.ToString(), "-- {querySelect}")))))); + + return (query, 5); + } + + public static (string, byte) EncountersIsIncludedQuery(string queryFilter, string encounterSummaryFilters, string queryTrim, string queryJoin = "") + { + var sbQuerySelect = new StringBuilder(); + var assembly = Assembly.GetExecutingAssembly(); + sbQuerySelect.Append(assembly.ReadResource("ExplorationQueryPhysicianData.sql")); + sbQuerySelect.Append(assembly.ReadResource("ExplorationQueryEncountersIdIncluded.sql")); + + var query = + ApplyQueryTrim(queryTrim, + ApplyQueryFilters(queryFilter, + ApplyEncounterSummaryFilters(encounterSummaryFilters, + ApplyQueryListOfEncounters(assembly.ReadResource("ExplorationQueryBase.sql"), + ApplyQuerySelect(sbQuerySelect.ToString(), "-- {querySelect}"))))); + + return (query, 2); + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/EXFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/EXFilterOptions.cs new file mode 100644 index 0000000..fb70a3a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/EXFilterOptions.cs @@ -0,0 +1,6 @@ +namespace Strata.ContinuousImprovement.Biz.Exploration.Filters +{ + public class EXFilterOptions + { + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/EXFilters.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/EXFilters.cs new file mode 100644 index 0000000..7ccd9f6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/EXFilters.cs @@ -0,0 +1,30 @@ +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Exploration.Opportunities; +using Strata.ContinuousImprovement.Biz.Filter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Exploration.Filters +{ + public class EXFilters : FiltersOpportunity + { + public EXFilters() : base() + { + } + + public override IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = base.GetFilters(context).ToList(); + + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + filters.Add(m => ("EX - " + m.OpportunityId.ToString()).Contains(OpportunitySearch) + || m.Name.Contains(OpportunitySearch)); + } + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/ExclusionCriteria.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/ExclusionCriteria.cs new file mode 100644 index 0000000..dffa63e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/ExclusionCriteria.cs @@ -0,0 +1,50 @@ +using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies; + +namespace Strata.ContinuousImprovement.Biz.Exploration.Filters +{ + public class ExclusionCriteria + { + + public ExclusionCriteriaType LosExclusionType { get; set; } + public double LosExclusionValue { get; set; } + public ExclusionCriteriaType CostExclusionType { get; set; } + public double CostExclusionValue { get; set; } + + public string GetTrimQueryString(CaseTypeFamilyTrimInfo trimInfo) + { + var queryString = ""; + if (LosExclusionType != ExclusionCriteriaType.None) queryString += $"OR (fpes.lengthofstay > {GetLosTrim(trimInfo)}) "; + if (CostExclusionType != ExclusionCriteriaType.None) queryString += $"OR (fpes.variabledirectcost > {GetCostTrim(trimInfo)}) "; + return queryString; + } + + private double GetLosTrim(CaseTypeFamilyTrimInfo trimInfo) + { + return LosExclusionType switch + { + ExclusionCriteriaType.Mad => (trimInfo.MadLos * LosExclusionValue) + trimInfo.MedianLos, + ExclusionCriteriaType.Iqr => (trimInfo.IqrLos * LosExclusionValue) + trimInfo.SeventyFivePercentileLos, + ExclusionCriteriaType.None => double.MaxValue, + _ => double.MaxValue + }; + } + + private double GetCostTrim(CaseTypeFamilyTrimInfo trimInfo) + { + return CostExclusionType switch + { + ExclusionCriteriaType.Mad => (trimInfo.MadVdc * CostExclusionValue) + trimInfo.MedianVdc, + ExclusionCriteriaType.Iqr => (trimInfo.IqrVdc * CostExclusionValue) + trimInfo.SeventyFivePercentileVdc, + ExclusionCriteriaType.None => double.MaxValue, + _ => double.MaxValue + }; + } + } + + public enum ExclusionCriteriaType + { + None, + Mad, + Iqr + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/Filter.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/Filter.cs new file mode 100644 index 0000000..a560733 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Filters/Filter.cs @@ -0,0 +1,153 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Exploration.Filters +{ + public class Filter + { + public DateTime DischargeDateStart { get; set; } + public DateTime DischargeDateEnd { get; set; } + public List FilterChipItems { get; set; } + } + + public class FilterChipItem + { + public ChipKey Key { get; set; } + public string Label { get; set; } + public string GlobalId { get; set; } + public OperatorType Operator { get; set; } + public ChipType ChipType { get; set; } + public List Filters { get; set; } + public bool? IsSelected { get; set; } + public IEnumerable DateRange { get; set; } + } + + public class FilterCollection + { + public IEnumerable QueryFilters { get; set; } + public IEnumerable EncounterSummaryFilters { get; set; } + } + + public class TreeNode + { + public string Id { get; set; } + public string DisplayText { get; set; } + } + + public enum ChipType + { + ScoreMember, + Boolean, + Special, + CostDriver, + DateRange, + Supply, + CostComponent, + SphCriteria + } + + public enum ChipKey + { + AdmitSourceID, + AdmitTypeID, + AgeCohortID, + AprDrgID, + CaseTypeFamilyID, + CostDriver, + DischargeStatusId, + EntityID, + EncounterID, + GenderID, + ICD10DXPrimaryDiagID, + ICD10PXPrimaryID, + InsurancePlan1ID, + IsGreaterThanZeroCharges, + LocationID, + Mortality, + MSDRGID, + NewBornFlagID, + PatientTypeID, + PrimaryCPTID, + PrimaryPhysicianID, + PrimaryQVIID, + RiskOfMortality, + SeverityOfIllness, + SourceSystemID, + DischargeDateID, + BilledCPT, + ChargeCode, + CostComponent, + Supply, + UBRevenueCode, + Department, + VariableDirectCost = 32, + LengthOfStay = 33, + MSDRGRollupID = 34, + SPHAdmitType, + SPHDischargeStatus, + SPHPatientType, + SPHGender, + SPHIsNewBorn, + SPHAgeRollup + } + + public enum OperatorType + { + In, + NotIn + } + + public static class ChipTypeExtensions + { + public static bool IsEncounterSummaryFilter(this ChipKey chipKey) + { + switch (chipKey) + { + case ChipKey.AdmitSourceID: + case ChipKey.AdmitTypeID: + case ChipKey.AgeCohortID: + case ChipKey.AprDrgID: + case ChipKey.DischargeStatusId: + case ChipKey.EntityID: + case ChipKey.EncounterID: + case ChipKey.GenderID: + case ChipKey.ICD10DXPrimaryDiagID: + case ChipKey.ICD10PXPrimaryID: + case ChipKey.InsurancePlan1ID: + case ChipKey.LocationID: + case ChipKey.MSDRGID: + case ChipKey.NewBornFlagID: + case ChipKey.PatientTypeID: + case ChipKey.PrimaryCPTID: + case ChipKey.PrimaryPhysicianID: + case ChipKey.SourceSystemID: + case ChipKey.DischargeDateID: + case ChipKey.PrimaryQVIID: + case ChipKey.VariableDirectCost: // Should we refactor so that this filters on our costing costs instead of FPES? + case ChipKey.LengthOfStay: + return true; + case ChipKey.CostDriver: + case ChipKey.RiskOfMortality: + case ChipKey.SeverityOfIllness: + case ChipKey.IsGreaterThanZeroCharges: + case ChipKey.Mortality: + case ChipKey.MSDRGRollupID: + case ChipKey.CaseTypeFamilyID: + return false; + } + return false; + } + + public static bool ContainsChips(string filtersJson, IEnumerable chips) + { + if (!string.IsNullOrEmpty(filtersJson) && filtersJson != "[]") + { + var additionalFilters = JsonConvert.DeserializeObject>(filtersJson); + return additionalFilters.Any(f => chips.Contains(f.Key)); + } + return false; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/IExplorationFilterService.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/IExplorationFilterService.cs new file mode 100644 index 0000000..d494895 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/IExplorationFilterService.cs @@ -0,0 +1,18 @@ +using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.StrataSphereCompare.Client.Models; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using static Strata.ContinuousImprovement.Biz.Exploration.ExplorationService; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public interface IExplorationFilterService + { + Task<(string QueryFilters, string EncounterSummaryFilters)> GetFilterStringsAsync(IEnumerable filterChipItems, IQueryParamBase queryParams, CancellationToken cancellationToken); + Task GetFiltersAsync(IEnumerable filterChipItems, IQueryParamBase queryParams, CancellationToken cancellationToken); + Task BuildDataFiltersAsync(CaseTypeFamily drillDownCaseTypeFamily, string drillDownCostDriver, Filters.Filter filters, bool isUsingAprdrg, bool forceIncludeOfCostDriverColumn, bool includeCodeColumn, CancellationToken cancellationToken); + FilterChipItem CreateCostDriverFilter(); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/IExplorationInfo.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/IExplorationInfo.cs new file mode 100644 index 0000000..69c536a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/IExplorationInfo.cs @@ -0,0 +1,15 @@ +using Strata.ContinuousImprovement.Biz.Exploration.Benchmark; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using System; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public interface IExplorationInfo + { + Guid ConfigurationGuid { get; set; } + ExclusionCriteria ExclusionCriteria { get; set; } + Filters.Filter Filters { get; set; } + int PeerGroupId { get; set; } + BenchmarkingInsightsGranularity Granularity { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/IExplorationService.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/IExplorationService.cs new file mode 100644 index 0000000..9b9b8e8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/IExplorationService.cs @@ -0,0 +1,19 @@ +using Strata.ContinuousImprovement.Biz.Exploration.Benchmark; +using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public interface IExplorationService + { + Task> GetCaseTypeFamilySummariesAsync(IExplorationInfo exploration, CancellationToken cancellationToken); + Task> GetCaseTypeFamilySummaryDetailsAsync(IExplorationInfo exploration, CancellationToken cancellationToken); + Task GetCaseTypeFamilyDetailsAsync(ExplorationDetailInfo exploration, CancellationToken cancellationToken); + Task> GetEncountersAsync(ExplorationDetailInfo exploration, Guid configurationGuid, CancellationToken cancellationToken); + Task> GetBenchmarkingDataForHeatMapAsync(IExplorationInfo exploration, BenchmarkingInsightsGranularity granularity, CancellationToken cancellationToken); + Task IsFlexExPerfLogging(CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/IWorkbookService.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/IWorkbookService.cs new file mode 100644 index 0000000..616ccb5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/IWorkbookService.cs @@ -0,0 +1,16 @@ +using Strata.Analytics.Models.Utils; +using Strata.Analytics.Models.Workbooks; +using Strata.ContinuousImprovement.Biz.DbContexts; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public interface IWorkbookService + { + Task CheckHasValidWorkbookAsync(int workbookId, CancellationToken cancellationToken); + Task CreateDefaultWorkbook(string workbookName, CancellationToken cancellationToken); + Task> GetDefaultCIUserRoleAssignmentsAsync(JazzDbContext dbContext, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Opportunities/EXOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Opportunities/EXOpportunity.cs new file mode 100644 index 0000000..ad1ced7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Opportunities/EXOpportunity.cs @@ -0,0 +1,52 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Exploration.Opportunities +{ + [Table("ExplorationOpportunity", Schema = "cci")] + public class EXOpportunity : Opportunity, IOpportunity + { + public EXOpportunity() + { + OpportunityGuid = Guid.NewGuid(); + DateCreated = DateTime.Now; + } + + public string Name { get; set; } + public string Description { get; set; } + public DateTime DateCreated { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long OpportunityId { get; set; } + public InitiativeStatuses InitiativeStatus { get; set; } = InitiativeStatuses.OnHold; + public int AnalyticsWorkbookId { get; set; } + + // IOpportunity properties needed to implement interface (these are typically properties from a view) + [NotMapped] + public new string OpportunityKey + { + get + { + return $"EX - {OpportunityId}"; + } + set { } + } + + [NotMapped] + public new bool HasInitiative { get; set; } + [NotMapped] + public new string Initiative { get; set; } + + public override void Update(IOpportunity updates) + { + base.Update(updates); + var exUpdates = updates as EXOpportunity; + if (exUpdates == null) return; + Name = exUpdates.Name; + Description = exUpdates.Description; + InitiativeStatus = exUpdates.InitiativeStatus; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Opportunities/EXOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Opportunities/EXOpportunityService.cs new file mode 100644 index 0000000..d823b43 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Opportunities/EXOpportunityService.cs @@ -0,0 +1,140 @@ +using ClosedXML.Excel; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration.Opportunities +{ + public class EXOpportunityService : OpportunityService, IEXOpportunityService + { + private readonly IHubContext _notificationHubContext; + private readonly IWorkbookService _workbookService; + private readonly ILogger _logger; + + public EXOpportunityService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IInitiativeService initiativeService, IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + IHubContext notificationHubContext, + IWorkbookService workbookService, + ILogger logger) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) + { + _notificationHubContext = notificationHubContext; + _workbookService = workbookService; + _logger = logger; + } + + public override async Task GetById(Guid opportunityGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + return dbContext.ExplorationOpportunities.FirstOrDefault(opp => opp.OpportunityGuid == opportunityGuid); + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return null; + + var opportunityContext = dbContext.Set(); + + return new EXFilterOptions + { + }; + } + + public async Task CreateAsync(EXOpportunity opportunity, string userName, CancellationToken cancellationToken) + { + var hubKey = userName + opportunity.Name; + var hub = _notificationHubContext.Clients.Group(hubKey); + + try + { + await using var dbContext = GetDbContext(cancellationToken).Result; + await dbContext.AddAsync(opportunity, cancellationToken); + await dbContext.SaveChangesAsync(); + await hub.SendExplorationOpportunityCreateSuccess(hubKey, opportunity.Name, opportunity.OpportunityGuid.ToString()); + return opportunity; + } + catch (Exception ex) + { + await hub.SendExplorationOpportunityCreateError(hubKey, opportunity.Name); + _logger.LogOpportunityError(ex, opportunity.Name, Guid.Empty); + return null; + } + } + + public async Task UpdateAsync(EXOpportunity opportunity, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opp = dbContext.ExplorationOpportunities.FirstOrDefault(opp => opp.OpportunityGuid == opportunity.OpportunityGuid); + if (opp == null) { return null; } + opp.Name = opportunity.Name; + opp.Description = opportunity.Description; + opp.IdentifiedSavings = opportunity.IdentifiedSavings; + opp.InitiativeStatus = opportunity.InitiativeStatus; + opp.Note = opportunity.Note; + opp.AnalyticsWorkbookId = opportunity.AnalyticsWorkbookId; + dbContext.Update(opp).Property(p => p.OpportunityId).IsModified = false; + await dbContext.SaveChangesAsync(); + return opp; + } + + public async Task DeleteAsync(Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opp = dbContext.ExplorationOpportunities.FirstOrDefault(opp => opp.OpportunityGuid == opportunityGuid); + dbContext.Remove(opp); + var result = await dbContext.SaveChangesAsync(); + return result > 0; + } + + public async Task CheckWorkbook(Guid opportunityGuid, CancellationToken cancellationToken) + { + var opportunity = await GetById(opportunityGuid, cancellationToken); + if (opportunity == null) { return; } + var hasValidWorkbook = await _workbookService.CheckHasValidWorkbookAsync(opportunity.AnalyticsWorkbookId, cancellationToken); + if (hasValidWorkbook) { return; } + opportunity.AnalyticsWorkbookId = await _workbookService.CreateDefaultWorkbook(opportunity.Name, cancellationToken); + var dbContext = await GetDbContext(cancellationToken); + dbContext.Update(opportunity); + await dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task ExportAsync(SortOptions sortOptions, EXFilters filters, CancellationToken cancellationToken) + { + var exportOptions = this.GetExportOptions(); + + // get data + var opportunities = await this.GetAllAsync(sortOptions, filters, cancellationToken); + var data = opportunities.Select(opp => new + { + Opportunity = opp.OpportunityKey, + opp.Name, + opp.IdentifiedSavings, + opp.Initiative, + IsHidden = opp.IsHidden ? 1 : 0, + opp.Note + }); + + // configure column formats + exportOptions.AddColumnOptions("Identified Savings", "Currency2Dec"); + exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\""); + + return ExportUtils.CreateExcelWorkbook(data, exportOptions); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/Opportunities/IEXOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/Opportunities/IEXOpportunityService.cs new file mode 100644 index 0000000..eb9f7b7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/Opportunities/IEXOpportunityService.cs @@ -0,0 +1,21 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration.Opportunities +{ + public interface IEXOpportunityService : IOpportunityService + { + Task GetById(Guid opportunityGuid, CancellationToken cancellationToken); + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + Task ExportAsync(SortOptions sortOptions, EXFilters filters, CancellationToken cancellationToken); + Task CreateAsync(EXOpportunity opportunity, string userName, CancellationToken cancellationToken); + Task UpdateAsync(EXOpportunity opportunity, CancellationToken cancellationToken); + Task DeleteAsync(Guid opportunityGuid, CancellationToken cancellationToken); + Task CheckWorkbook(Guid opportunityGuid, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Exploration/WorkbookService.cs b/src/Strata.ContinuousImprovement.Biz/Exploration/WorkbookService.cs new file mode 100644 index 0000000..41a904c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Exploration/WorkbookService.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.Analytics.Client; +using Strata.Analytics.Models.Utils; +using Strata.Analytics.Models.Workbooks; +using Strata.ApiCommunication.Http.Exceptions; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Security; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Exploration +{ + public class WorkbookService : IWorkbookService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IAnalyticsService _analyticsService; + private readonly ISimpleSecurityService _simpleSecurityService; + private readonly ILogger _logger; + + public WorkbookService(IAsyncDbContextFactory dbContextFactory, + IAnalyticsService analyticsService, + ISimpleSecurityService simpleSecurityService, + ILogger logger) + { + _dbContextFactory = dbContextFactory; + _analyticsService = analyticsService; + _simpleSecurityService = simpleSecurityService; + _logger = logger; + } + + public async Task CheckHasValidWorkbookAsync(int workbookId, CancellationToken cancellationToken) + { + if (workbookId == 0) return false; + try + { + var workbook = await _analyticsService.GetWorkbookAsync(workbookId, cancellationToken); + return true; + } + catch (StrataHttpException ex) //GetWorkbookAsync throws a NotFound if the workbook doesn't exist + { + if (ex.StatusCode == System.Net.HttpStatusCode.NotFound) return false; + return true; + } + catch + { + //We don't want to create another workbook if we catch any other type of exception + return true; + } + } + + public async Task CreateDefaultWorkbook(string workbookName, CancellationToken cancellationToken) + { + var workbookCreateInfo = new WorkbookCreateInfo() { Name = workbookName }; + var workbook = await _analyticsService.CreateWorkbookAsync(workbookCreateInfo, cancellationToken); + var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + var roleAssignment = await GetDefaultCIUserRoleAssignmentsAsync(dbContext, cancellationToken); + await _analyticsService.SaveRoleAssignmentsAsync(workbook.WorkbookId, roleAssignment, cancellationToken); + return workbook.WorkbookId; + } + + public async Task> GetDefaultCIUserRoleAssignmentsAsync(JazzDbContext dbContext, CancellationToken cancellationToken) + { + var defaults = await _simpleSecurityService.GetSimplePermissionDefaults(SecurableEntityType.Opportunity, cancellationToken); + var roleAssignment = new Batch + { + Updates = defaults.Select(dflt => + new WorkbookRoleAssignmentSaveInfo + { + IdentityGuid = dflt.IdentityGUID, + IdentityName = dflt.IdentityName, + IdentityType = Analytics.Models.Security.IdentityType.UserGroup, + Role = dflt.WriteValue ? WorkbookRole.Editor : WorkbookRole.Viewer + }) + }; + return roleAssignment; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Export/ColumnOptions.cs b/src/Strata.ContinuousImprovement.Biz/Export/ColumnOptions.cs new file mode 100644 index 0000000..c087f2a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Export/ColumnOptions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Export +{ + public class ColumnOptions + { + public string Column { get; set; } + public string Format { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Export/ExportOptions.cs b/src/Strata.ContinuousImprovement.Biz/Export/ExportOptions.cs new file mode 100644 index 0000000..da26052 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Export/ExportOptions.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Export +{ + public class ExportOptions + { + public string DefaultWorksheetName { get; set; } + public string EmptyMessage { get; set; } + + public IList ColumnOptions { get; set; } + + public ExportOptions() + { + DefaultWorksheetName = "Sheet1"; + EmptyMessage = "No Data Available"; + ColumnOptions = new List(); + } + + public void AddColumnOptions(string column, string format) + { + this.ColumnOptions.Add(new ColumnOptions() { Column = column, Format = format }); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Export/ExportUtils.cs b/src/Strata.ContinuousImprovement.Biz/Export/ExportUtils.cs new file mode 100644 index 0000000..d4f16b2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Export/ExportUtils.cs @@ -0,0 +1,327 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Export +{ + [ExcludeFromCodeCoverage] + public static class ExportUtils + { + /// + /// 0 General + ///1 0 + ///2 0.00 + ///3 #,##0 + ///4 #,##0.00 + ///9 0% + ///10 0.00% + ///11 0.00E+00 + ///12 # ?/? + ///13 # ??/?? + ///14 d/m/yyyy + ///15 d-mmm-yy + ///16 d-mmm + ///17 mmm-yy + ///18 h:mm tt + ///19 h:mm:ss tt + ///20 H:mm + ///21 H:mm:ss + ///22 m/d/yyyy H:mm + ///37 #,##0 ;(#,##0) + ///38 #,##0 ;[Red](#,##0) + ///39 #,##0.00;(#,##0.00) + ///40 #,##0.00;[Red](#,##0.00) + ///45 mm:ss + ///46 [h]:mm:ss + ///47 mmss.0 + ///48 ##0.0E+0 + ///49 @ + /// + internal enum NumberFormatId + { + General, + Zero, + ZeroDotZeroZero, + PoundCommaPoundPoundZero, + PoundCommaPoundPoundZeroDotZeroZero, + ZeroPercent = 9, + ZeroDotZeroZeroPercent = 10, + ScientificNotation = 11, + PoundSpaceQuestionSlashQuestion = 12, + PoundSpaceQuestionQuestionSlashQuestionQuestion = 13, + ShortDateSlash = 14, + ShortDateDash = 15, + DDashMMM = 16, + MMMDashYY = 17, + ShortTime = 18, + ShortTimeSeconds = 19, + ShortTime24 = 20, + ShortTimeSeconds24 = 21, + ShortDateTime = 22, + PoundCommaPoundPoundZeroNegative = 37, + PoundCommaPoundPoundZeroRedNegative = 38, + PoundCommaPoundPoundZeroDotZeroZeroNegative = 39, + PoundCommaPoundPoundZeroDotZeroZeroRedNegative = 40, + MinutesSeconds = 45, + HoursOptionalMinutesSeconds = 46, + MinutesSecondsMilliseconds = 47, + PoundPoundScientificNotation = 48, + HideZeroValues = 49 + } + + internal static Dictionary NumberFormatDictionary => new Dictionary() + { + {"General", 0}, + {"0", 1}, + {"Accounting",1}, + {"0.00", 2}, + {"Accounting2Dec", 2}, + {"#,##0", 3}, + {"Currency",3}, + {"#,##0.00", 4}, + {"Currency2Dec", 4}, + {"0%", 9}, + {"ZeroPercent", 9}, + {"0.00%", 10}, + {"ZeroPerentWithDecimals", 10}, + {"0.00E+00", 11}, + {"Scientific", 11}, + {"# ?/?", 12}, + {"Fractions", 12}, + {"# ??/??", 13}, + {"mm-dd-yy", 14}, + {"ShortDateSlash", 14}, + {"d-mmm-yy", 15}, + {"ShortDateDash", 15}, + {"d-mmm", 16}, + {"DDashMMM", 16}, + {"mmm-yy", 17}, + {"MMMDashYY", 17}, + {"h:mm AM/PM", 18}, + {"ShortTime", 18}, + {"h:mm:ss AM/PM", 19}, + {"ShortTimeSeconds",19}, + {"h:mm", 20}, + {"ShortTime24", 20}, + {"h:mm:ss", 21}, + {"ShortTimeSeconds24",21}, + {"m/d/yy h:mm", 22}, + {"ShortDateTime",22}, + {"#,##0 ;(#,##0)", 37}, + {"NoLeadingZeroNegative",37}, + {"#,##0 ;[Red](#,##0)", 38}, + {"NoLeadingZeroRedNegative",38}, + {"#,##0.00;(#,##0.00)", 39}, + {"NoLeadingZeroDecimalsNegative",39}, + {"#,##0.00;[Red](#,##0.00)", 40}, + {"NoLeadingZeroDecimalsRedNegative",40}, + {"mm:ss", 45}, + {"MinutesSeconds", 45}, + {"[h]:mm:ss", 46}, + {"HoursOptionalMinutesSeconds",46}, + {"mmss.0", 47}, + {"MinutesSecondsMilliseconds", 47}, + {"##0.0E+0", 48}, + {"ScientificNotation",48}, + {"@", 49}, + {"HideZeroValues", 49} + }; + + public static string ExcelCurrencyFormatWithDecimals + { + get + { + var currencySymbol = CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol; + return $@"{currencySymbol}#,##0.00_);({currencySymbol}#,##0.00)"; + } + } + + public static string ExcelCurrencyFormatWithoutDecimals + { + get + { + var currencySymbol = CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol; + return $@"{currencySymbol}#,##0;({currencySymbol}#,##0)"; + } + } + + public static string CellName(this IXLWorksheet ws, int row, int col) => ws.Cell(row, col).Address.ToString(); + + public static IXLWorksheet WriteFormField(this IXLWorksheet ws, int row, int col, string label, string value) + { + ws.Cell(row, col).Value = $"{label}:"; + ws.Cell(row, col + 1).Value = value; + return ws; + } + + public static IXLWorksheet WriteSheetTitle(this IXLWorksheet ws, int row, int col, int offset, string title) + { + var cellRow = ws.Row(row); + cellRow.Height = 30; + var cells = ws.Range(row, col, row, offset); + cells.Merge(); + cells.Style.Font.Bold = true; + cells.Style.Font.FontColor = XLColor.FromArgb(31, 73, 125); + cells.Style.Font.FontSize = 18; + cells.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center; + cells.Value = title; + return ws; + } + + public static IXLWorksheet WriteSheetSubTitle(this IXLWorksheet ws, int row, int col, int offset, string subtitle) + { + var cells = ws.Range(row, col, row, offset); + cells.Merge(); + cells.Style.Font.Italic = true; + cells.Style.Font.FontColor = XLColor.FromArgb(237, 125, 49); + cells.Value = subtitle; + return ws; + } + + public static IXLWorksheet WriteHeader(this IXLWorksheet ws, int row, int col, int offset, string label) + { + var cells = ws.Range(row, col, row, offset); + cells.Merge(); + cells.Style.Font.Bold = true; + cells.Style.Font.FontColor = XLColor.FromArgb(31, 73, 125); + cells.Style.Font.FontSize = 16; + cells.Style.Border.BottomBorder = XLBorderStyleValues.Medium; + cells.Style.Border.BottomBorderColor = XLColor.FromArgb(148, 179, 215); + cells.Value = label; + return ws; + } + + public static IXLWorksheet WriteCellValue(this IXLWorksheet ws, int row, int col, object value) + { + var cell = ws.Cell(row, col); + cell.Value = value; + return ws; + } + + internal static IXLWorksheet WriteHeading(this IXLWorksheet ws, int row, int col, string value) + { + var cell = ws.Cell(row, col); + cell.Style.Font.Bold = true; + cell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center; + cell.Value = value; + return ws; + } + + public static IXLWorksheet CreateTitlePage(this XLWorkbook wb, string title, string subTitle) + { + var ws = wb.AddWorksheet(title); + ws.WriteSheetTitle(1, 1, 8, title) + .WriteHeader(3, 1, 8, subTitle) + .WriteFormField(5, 1, "Report Date", DateTime.Today.ToString("g")); + ws.Columns().AdjustToContents(); + return ws; + } + + public static IXLWorksheet CreateWorksheet(this XLWorkbook wb, DataTable items, string worksheetName, int headerRow = 1) + { + wb.AddWorksheet(worksheetName); + var ws = wb.Worksheet(worksheetName); + var table = ws.Cell(headerRow, 1).InsertTable(items); + var columns = table.ColumnsUsed().Select(t => t.ColumnNumber()); + foreach (var colNo in columns) + { + var heading = (string)ws.Cell(headerRow, colNo).Value; + ws.Cell(headerRow, colNo).Value = heading.SplitCamelCase(); + } + ws.Columns().AdjustToContents(); + return ws; + } + + public static IXLWorksheet CreateWorksheet(this XLWorkbook wb, IEnumerable items, string worksheetName) + { + wb.AddWorksheet(worksheetName); + var ws = wb.Worksheet(worksheetName); + var table = ws.Cell(1, 1).InsertTable(items); + var columns = table.ColumnsUsed().Select(t => t.ColumnNumber()); + foreach (var colNo in columns) + { + var heading = (string)ws.Cell(1, colNo).Value; + ws.Cell(1, colNo).Value = heading.SplitCamelCase(); + } + ws.Columns().AdjustToContents(); + return ws; + } + + public static XLWorkbook CreateExcelWorkbook(DataTable data, ExportOptions options, int headerRow = 1) + { + if (data.Rows.Count == 0) + { + return GetEmptyWorkbook(options); + } + + // load data into worksheet + var workbook = new XLWorkbook(); + var ws = workbook.CreateWorksheet(data, options.DefaultWorksheetName, headerRow); + + // format columns + foreach (var columnOption in options.ColumnOptions) + { + ws.SetColumnFormatByHeader(columnOption.Column, columnOption.Format); + } + + return workbook; + } + + public static XLWorkbook CreateExcelWorkbook(IEnumerable data, ExportOptions options) + { + if (data is null || !data.Any()) + { + return GetEmptyWorkbook(options); + } + + // load data into worksheet + var workbook = new XLWorkbook(); + var ws = workbook.CreateWorksheet(data, options.DefaultWorksheetName); + + // format columns + foreach (var columnOption in options.ColumnOptions) + { + ws.SetColumnFormatByHeader(columnOption.Column, columnOption.Format); + } + + return workbook; + } + private static XLWorkbook GetEmptyWorkbook(ExportOptions options) + { + var workbook = new XLWorkbook(); + var ws = workbook.AddWorksheet(options.DefaultWorksheetName); + ws.Cell(1, 1).Value = options.EmptyMessage; + return workbook; + } + + internal static IXLCell CellByHeader(this IXLWorksheet ws, string columnHeader) + { + return ws.Tables.First().HeadersRow().CellsUsed(c => c.Value.ToString() == columnHeader).FirstOrDefault(); + } + + internal static IXLColumn ColumnByHeader(this IXLWorksheet ws, string columnHeader) + { + return ws.Tables.FirstOrDefault()?.HeadersRow() + .CellsUsed(c => c.Value.ToString() == columnHeader).FirstOrDefault()?.WorksheetColumn(); + } + + internal static void SetColumnFormatByHeader(this IXLWorksheet ws, string columnHeader, string formatName) + { + var column = ws.ColumnByHeader(columnHeader); + if (column == null) throw new ArgumentOutOfRangeException(nameof(columnHeader)); + if (NumberFormatDictionary.TryGetValue(formatName, out var formatId)) + { + column.Style.NumberFormat.NumberFormatId = formatId; + } + else + { + column.Style.NumberFormat.Format = formatName; + } + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/CostComponentRollup.cs b/src/Strata.ContinuousImprovement.Biz/Filter/CostComponentRollup.cs new file mode 100644 index 0000000..329a986 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/CostComponentRollup.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Filter +{ + [Table("CostComponentRollup", Schema = "dss")] + public class CostComponentRollup + { + [Key] + public Guid CostComponentRollupGUID { get; set; } + public string Name { get; set; } + public ICollection CostComponents { get; set; } = new List(); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/DimCostComponent.cs b/src/Strata.ContinuousImprovement.Biz/Filter/DimCostComponent.cs new file mode 100644 index 0000000..2b119a6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/DimCostComponent.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Filter +{ + [Table("DimCostComponent", Schema = "dss")] + public class DimCostComponent + { + [Key] + public int CostComponentID { get; set; } + public string Name { get; set; } + public CostComponentRollup CostComponentRollup { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/Expressions/ConfigurationGuidFilter.cs b/src/Strata.ContinuousImprovement.Biz/Filter/Expressions/ConfigurationGuidFilter.cs new file mode 100644 index 0000000..21a0077 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/Expressions/ConfigurationGuidFilter.cs @@ -0,0 +1,15 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Filter.Expressions +{ + internal static class ConfigurationGuidFilter + { + public static void AddConfigurationGuidFilter(this IList>> filters, Guid ConfigurationGuid) where T:IWithConfigurationGuid + { + filters.Add(m => m.ConfigurationGuid.Equals(ConfigurationGuid)); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/Expressions/IdentifiedSavingsTotalFilter.cs b/src/Strata.ContinuousImprovement.Biz/Filter/Expressions/IdentifiedSavingsTotalFilter.cs new file mode 100644 index 0000000..66c94d5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/Expressions/IdentifiedSavingsTotalFilter.cs @@ -0,0 +1,16 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Filter.Expressions +{ + internal static class IdentifiedSavingsTotalFilter + { + public static void AddIdentifiedSavingsTotalFilter(this IList>> filters, double? identifiedSavingsTotal) where T : IWithIdentifiedSavingsTotal + { + if (identifiedSavingsTotal is not null) + filters.Add(m => m.IdentifiedSavingsTotal >= identifiedSavingsTotal); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/FilterListItem.cs b/src/Strata.ContinuousImprovement.Biz/Filter/FilterListItem.cs new file mode 100644 index 0000000..c8c90dd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/FilterListItem.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Filter +{ + public class FilterListItem + { + public string Key { get; set; } + public string Title { get; set; } + public IEnumerable Children { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/FilterMember.cs b/src/Strata.ContinuousImprovement.Biz/Filter/FilterMember.cs new file mode 100644 index 0000000..94f80f8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/FilterMember.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.Filter +{ + public class FilterMember + { + public string Id { get; set; } + public string Text { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/FilterService.cs b/src/Strata.ContinuousImprovement.Biz/Filter/FilterService.cs new file mode 100644 index 0000000..c1ebdde --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/FilterService.cs @@ -0,0 +1,112 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.ChargeCode; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using Strata.StrataSphereCompare.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Filter +{ + public class FilterService : IFilterService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ILogger _logger; + private readonly IStrataSphereCompareService _strataSphereCompareService; + + /// + /// SPH Column Keys we can fetch from API to get filter values + /// + private static readonly HashSet SupportedSphChipKeys = new() + { + ChipKey.SPHAdmitType, + ChipKey.SPHDischargeStatus, + ChipKey.SPHPatientType, + ChipKey.SPHGender, + ChipKey.SPHAgeRollup + }; + + public FilterService( + IAsyncDbContextFactory dbContextFactory, + ILogger logger, + IStrataSphereCompareService strataSphereCompareService) + { + _dbContextFactory = dbContextFactory; + _logger = logger; + _strataSphereCompareService = strataSphereCompareService; + } + + private async Task GetDbContextAsync(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task> GetSupplyList(CancellationToken cancellationToken) + { + try + { + var dbContext = await GetDbContextAsync(cancellationToken); + return await dbContext.DimSupply.Select(supply => new FilterListItem { Key = supply.SupplyID.ToString(), Title = supply.Name }).ToListAsync(cancellationToken); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + } + + return new List(); + } + + public async Task> GetCostComponentList(CancellationToken cancellationToken) + { + try + { + var dbContext = await GetDbContextAsync(cancellationToken); + + + var result = await dbContext.CostComponentRollups.GroupBy(a => a.Name).Select(group => new FilterListItem() + { + Key = group.Key, + Children = group.SelectMany(a => a.CostComponents).Select(c => new FilterListItem() { Key = @$"{group.Key}|{c.Name}", Title = c.Name }).ToList() + }).ToListAsync(cancellationToken); + + + result.ForEach(item => + { + item.Title = $"{item.Key} ({item.Children.DistinctBy(x => x.Title).Count()})"; + item.Children = item.Children.DistinctBy(x => x.Title); + }); + + return result.Where(x => x.Children.Any()).ToList(); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + } + + return new List(); + } + + public async Task> GetSphCriteriaList(ChipKey chipKey, CancellationToken cancellationToken) + { + ValidateIsSupportedSphKey(chipKey); + var sphColumnName = chipKey.ToString(); + var values = await _strataSphereCompareService.GetDistinctFilterValuesAsync(sphColumnName, cancellationToken); + return values.Where(x => !string.IsNullOrEmpty(x)) + .Select(type => new FilterListItem { Key = type, Title = type }).OrderBy(x => x.Key).ToList(); + } + + private static void ValidateIsSupportedSphKey(ChipKey chipKey) + { + if (!SupportedSphChipKeys.Contains(chipKey)) + { + throw new ArgumentException($"Unsupported SPH chip key '{chipKey}'.", nameof(chipKey)); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/FiltersOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Filter/FiltersOpportunity.cs new file mode 100644 index 0000000..0891bb8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/FiltersOpportunity.cs @@ -0,0 +1,44 @@ +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter.Expressions; +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Filter +{ + public class FiltersOpportunity : IFiltersOpportunity where T : IOpportunity + { + public Guid ConfigurationGuid { get; set; } + public int? IdentifiedSavings { get; set; } + public bool? ViewVisible { get; set; } + public bool ExcludeInitiatives { get; set; } + public string OpportunitySearch { get; set; } + + public FiltersOpportunity() + { + ConfigurationGuid = Guid.Empty; + IdentifiedSavings = null; + ViewVisible = null; + ExcludeInitiatives = false; + OpportunitySearch = ""; + } + + public virtual IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = new List>>(); + + filters.AddConfigurationGuidFilter(ConfigurationGuid); + + if (ViewVisible.HasValue) + filters.Add(m => ViewVisible.Value.Equals(!(m).IsHidden)); + + if (IdentifiedSavings is not null) + filters.Add(m => Math.Round(m.IdentifiedSavings) >= IdentifiedSavings); + if (ExcludeInitiatives) + filters.Add(m => (m).InitiativeGuid.Equals(Guid.Empty)); + + return filters; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/IFilterService.cs b/src/Strata.ContinuousImprovement.Biz/Filter/IFilterService.cs new file mode 100644 index 0000000..8e07d07 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/IFilterService.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Filter +{ + public interface IFilterService + { + Task> GetSupplyList(CancellationToken cancellationToken); + Task> GetCostComponentList(CancellationToken cancellationToken); + Task> GetSphCriteriaList(ChipKey chipKey, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/IFilters.cs b/src/Strata.ContinuousImprovement.Biz/Filter/IFilters.cs new file mode 100644 index 0000000..81425a3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/IFilters.cs @@ -0,0 +1,15 @@ +using Strata.ContinuousImprovement.Biz.DbContexts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Filter +{ + public interface IFilters + { + IEnumerable>> GetFilters(JazzDbContext context); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/IFiltersOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Filter/IFiltersOpportunity.cs new file mode 100644 index 0000000..6cb7334 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/IFiltersOpportunity.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; + +namespace Strata.ContinuousImprovement.Biz.Filter +{ + public interface IFiltersOpportunity : IFilters where T : IOpportunity + { + public Guid ConfigurationGuid { get; set; } + public int? IdentifiedSavings { get; set; } + bool? ViewVisible { get; set; } + bool ExcludeInitiatives { get; set; } + string OpportunitySearch { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/IFiltersSummary.cs b/src/Strata.ContinuousImprovement.Biz/Filter/IFiltersSummary.cs new file mode 100644 index 0000000..1f28134 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/IFiltersSummary.cs @@ -0,0 +1,16 @@ +using Strata.ContinuousImprovement.Biz.DbContexts; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Filter +{ + public interface IFiltersSummary + { + public bool ExportFiltered { get; set; } + public Guid ConfigurationGuid { get; set; } + public int? IdentifiedSavings { get; set; } + + IEnumerable>> GetOpportunitiesWithCustomExpressions(JazzDbContext context) where TClass : class; + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Filter/SeasonalityType.cs b/src/Strata.ContinuousImprovement.Biz/Filter/SeasonalityType.cs new file mode 100644 index 0000000..3d65417 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Filter/SeasonalityType.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.Filter +{ + public enum SeasonalityType + { + WithAndWithout = -1, + Without = 0, + With = 1 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FiscalMonths/FiscalMonth.cs b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/FiscalMonth.cs new file mode 100644 index 0000000..1021c6a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/FiscalMonth.cs @@ -0,0 +1,44 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.FiscalMonths +{ + [Table("DimFiscalMonth", Schema = "fw")] + public class FiscalMonth + { + [Key] + public Guid MemberGuid { get; set; } + public string Name { get; set; } + public byte FiscalMonthId { get; set; } + public string FiscalMonthCode { get; set; } + public byte SortOrder { get; set; } + public int DaysInMonth { get; set; } + public int YtdDaysInMonth { get; set; } + public int DaysInLeapYearMonth { get; set; } + public int YtdDaysInLeapYearMonth { get; set; } + public string MonthColumnName { get; set; } + public string Abbreviation { get; set; } + public bool IsReal { get; set; } + + public FiscalMonth(Guid memberGUID, byte fiscalMonthID, byte sortOrder, string fiscalMonthCode, string name, string abbreviation, int daysInMonth, int yTDDaysInMonth, string monthColumnName, int daysInLeapYearMonth, int yTDDaysInLeapYearMonth) + { + + MemberGuid = memberGUID; + FiscalMonthId = fiscalMonthID; + SortOrder = sortOrder; + FiscalMonthCode = fiscalMonthCode; + Name = name; + Abbreviation = abbreviation; + DaysInMonth = daysInMonth; + YtdDaysInMonth = yTDDaysInMonth; + MonthColumnName = monthColumnName; + DaysInLeapYearMonth = daysInLeapYearMonth; + YtdDaysInLeapYearMonth = yTDDaysInLeapYearMonth; + } + + public FiscalMonth() + { + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FiscalMonths/FiscalMonthExtensions.cs b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/FiscalMonthExtensions.cs new file mode 100644 index 0000000..744ab7c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/FiscalMonthExtensions.cs @@ -0,0 +1,30 @@ +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.FiscalMonths +{ + internal static class FiscalMonthExtensions + { + internal static Dictionary GetFiscalMonthDictionary(this IEnumerable fiscalMonths, DateTime orgDefinedEndDate) + { + var lastFiscalMonth = fiscalMonths.First(fm => fm.SortOrder == 12); + var endDate = orgDefinedEndDate; + var startDate = endDate.GetStartOrEndDateOfYearPeriod(-1); + var yearMonthDictionary = new Dictionary(); + + while (startDate <= endDate) + { + var fiscalYearID = startDate.Month <= lastFiscalMonth.FiscalMonthId + ? startDate.Year + : startDate.Year + 1; + + yearMonthDictionary.Add(fiscalMonths.First(fm => fm.FiscalMonthId == (byte)startDate.Month).FiscalMonthId, (short)fiscalYearID); + startDate = startDate.AddMonths(1); + } + + return yearMonthDictionary; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FiscalMonths/FiscalMonthResolver.cs b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/FiscalMonthResolver.cs new file mode 100644 index 0000000..56e61e8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/FiscalMonthResolver.cs @@ -0,0 +1,152 @@ +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Schema.Client; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.FiscalMonths +{ + /// + /// This is just a starting point for this + /// + public class FiscalMonthResolver : IFiscalMonthResolver + { + private readonly ISchemaServiceClient _schemaServiceClient; + private readonly ReadOnlyCollection _fiscalMonthsBySortOrder; + private readonly ReadOnlyCollection _fiscalMonthsByMonthId; + private readonly ILogger _logger; + + public FiscalMonthResolver(ISchemaServiceClient schemaServiceClient, ILogger logger) + { + _logger = logger; + _schemaServiceClient = schemaServiceClient; + + var task = Task.Run(async () => await GetFiscalMonthsAsync().ConfigureAwait(false)); + try + { + var result = task.Result; + if (result == null || !result.Any()) return; + var fiscalMonths = result.Where(fm => fm.IsReal).ToList(); + _fiscalMonthsBySortOrder = new ReadOnlyCollection(fiscalMonths); + _fiscalMonthsByMonthId = new ReadOnlyCollection(_fiscalMonthsBySortOrder.OrderBy(p => p.FiscalMonthId).ToList()); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + } + } + + public async Task> FiscalMonthDictionary(DateTime date) + { + var fiscalMonths = await GetFiscalMonthsAsync(); + return fiscalMonths.GetFiscalMonthDictionary(date); + } + + public FiscalMonth FirstFiscalMonth() + => GetByFiscalMonthSortOrder(1); + + public FiscalMonth LastFiscalMonth() + => GetByFiscalMonthSortOrder(12); + + public FiscalMonth GetByFiscalMonthId(int fiscalMonthId) + { + if (fiscalMonthId < 1 || fiscalMonthId > 12) + { + throw new ArgumentOutOfRangeException("fiscalMonthId"); + } + + return _fiscalMonthsByMonthId[fiscalMonthId - 1]; + } + + public FiscalMonth GetNextFiscalMonth(int currFiscalMonthId) + { + if (currFiscalMonthId < 1 || currFiscalMonthId > 12) + { + throw new ArgumentOutOfRangeException("currFiscalMonthId"); + } + + return GetFiscalMonthByDifference(currFiscalMonthId, 1); + } + + public FiscalMonth GetPriorFiscalMonth(int currFiscalMonthId) + { + if (currFiscalMonthId < 1 || currFiscalMonthId > 12) + { + throw new ArgumentOutOfRangeException("currFiscalMonthId"); + } + + return GetFiscalMonthByDifference(currFiscalMonthId, -1); + } + + public int GetDifferenceBetweenFiscalMonths(int origMonthID, int newMonthID) + { + if (origMonthID == newMonthID) + { + return 0; + } + else if (origMonthID < newMonthID) + { + return newMonthID - origMonthID; + } + else + { + return -1 * (origMonthID - newMonthID); + } + } + + public FiscalMonth GetFiscalMonthByDifference(int currFiscalMonthId, int monthDifference) + { + if (currFiscalMonthId < 1 || currFiscalMonthId > 12) + { + throw new ArgumentOutOfRangeException("currFiscalMonthId"); + } + var nextFiscalMonthId = currFiscalMonthId + monthDifference; + if (nextFiscalMonthId < 1) + { + nextFiscalMonthId = 12 + nextFiscalMonthId; + } + else if (nextFiscalMonthId > 12) + { + nextFiscalMonthId = nextFiscalMonthId - 12; + } + + return _fiscalMonthsByMonthId[nextFiscalMonthId - 1]; + } + + public FiscalMonth GetByFiscalMonthSortOrder(int sortOrder) + { + if (sortOrder < 1 || sortOrder > 12) + { + throw new ArgumentOutOfRangeException("sortOrder"); + } + + return _fiscalMonthsBySortOrder?[sortOrder - 1] ?? default(FiscalMonth); + } + + public ReadOnlyCollection AllFiscalMonths + { + get { return _fiscalMonthsBySortOrder; } + } + + private async Task> GetFiscalMonthsAsync() + { + var fiscalMonths = Enumerable.Empty(); + try + { + var fiscalMonthDimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Fiscal Month") + ?? throw new ArgumentException("Fiscal Month dimension not found on client."); + fiscalMonths = await _schemaServiceClient.GetDimensionMembersByDimensionGuidAsync(fiscalMonthDimension.DimensionGuid); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + } + return fiscalMonths.OrderBy(fm => fm.SortOrder).ToList(); + } + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/FiscalMonths/IFiscalMonthResolver.cs b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/IFiscalMonthResolver.cs new file mode 100644 index 0000000..4c6ba53 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/IFiscalMonthResolver.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.FiscalMonths +{ + public interface IFiscalMonthResolver + { + Task> FiscalMonthDictionary(DateTime date); + FiscalMonth FirstFiscalMonth(); + FiscalMonth LastFiscalMonth(); + FiscalMonth GetByFiscalMonthId(int fiscalMonthId); + ReadOnlyCollection AllFiscalMonths { get; } + + FiscalMonth GetByFiscalMonthSortOrder(int sortOrder); + int GetDifferenceBetweenFiscalMonths(int origMonthID, int newMonthID); + FiscalMonth GetFiscalMonthByDifference(int currFiscalMonthId, int monthDifference); + FiscalMonth GetNextFiscalMonth(int currFiscalMonthId); + FiscalMonth GetPriorFiscalMonth(int currFiscalMonthId); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/FiscalMonths/TrackingMonth.cs b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/TrackingMonth.cs new file mode 100644 index 0000000..1be1a2c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FiscalMonths/TrackingMonth.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.FiscalMonths +{ + public class TrackingMonth + { + public string Abbreviation { get; set; } + public bool IsAfterEffectiveDate { get; set; } + public readonly bool IsBeforeTrackingDate; + public readonly bool IsCurrentMonth; + + public TrackingMonth(string abbreviation, bool isAfterEffectiveDate, bool isBeforeTrackingDate, bool isCurrentMonth) + { + Abbreviation = abbreviation; + IsAfterEffectiveDate = isAfterEffectiveDate; + IsBeforeTrackingDate = isBeforeTrackingDate; + IsCurrentMonth = isCurrentMonth; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FFFInitiativeTrackingData.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FFFInitiativeTrackingData.cs new file mode 100644 index 0000000..185360e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FFFInitiativeTrackingData.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Initiatives +{ + [Table("FinancialFreeFormInitiativeTrackingData", Schema = "cci")] + public class FFFInitiativeTrackingData + { + [Key] + public Guid InitiativeGUID { get; set; } + [Precision(15, 4)] + public decimal Month01Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month02Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month03Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month04Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month05Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month06Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month07Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month08Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month09Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month10Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month11Actual { get; set; } = 0; + [Precision(15, 4)] + public decimal Month12Actual { get; set; } = 0; + + public FFFInitiativeTrackingData(Guid initiativeGUID, decimal month01Actual, decimal month02Actual, decimal month03Actual, decimal month04Actual, decimal month05Actual, decimal month06Actual, decimal month07Actual, decimal month08Actual, decimal month09Actual, decimal month10Actual, decimal month11Actual, decimal month12Actual) + { + this.InitiativeGUID = initiativeGUID; + this.Month01Actual = month01Actual; + this.Month02Actual = month02Actual; + this.Month03Actual = month03Actual; + this.Month04Actual = month04Actual; + this.Month05Actual = month05Actual; + this.Month06Actual = month06Actual; + this.Month07Actual = month07Actual; + this.Month08Actual = month08Actual; + this.Month09Actual = month09Actual; + this.Month10Actual = month10Actual; + this.Month11Actual = month11Actual; + this.Month12Actual = month12Actual; + } + + public FFFInitiativeTrackingData(Guid initiativeGUID) + { + InitiativeGUID = initiativeGUID; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FFInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FFInitiativeService.cs new file mode 100644 index 0000000..d38b06e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FFInitiativeService.cs @@ -0,0 +1,121 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Encounter.Initiatives; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Initiatives +{ + public class FFInitiativeService : IFFInitiativeService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IInitiativeSavingsService _initiativeSavingsService; + private readonly IAuthorizationServiceClient _authorizationServiceClient; + private readonly IUpdateFactOpportunitySavingsService _updateFactOpportunitySavingsService; + private readonly ILogger _logger; + + public FFInitiativeService( + IAsyncDbContextFactory dbContextFactory, + IInitiativeSavingsService initiativeSavingsService, + IAuthorizationServiceClient authorizationServiceClient, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger logger) + { + _dbContextFactory = dbContextFactory; + _initiativeSavingsService = initiativeSavingsService; + _authorizationServiceClient = authorizationServiceClient; + _updateFactOpportunitySavingsService = updateFactOpportunitySavingsService; + _logger = logger; + } + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task CreateInitiative(Guid dbGuid, Initiative initiative, Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunity = await dbContext.FFOpportunities.SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + + if (opportunity == null) { return Guid.Empty; } + try + { + _initiativeSavingsService.ConfigureRampUps(dbContext, initiative, opportunity.ConfigurationGuid); + + initiative.ConfigurationGUID = opportunity.ConfigurationGuid; + await dbContext.Initiatives.AddAsync(initiative, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + + if (opportunity.MetricType == Opportunities.MetricType.Financial) + { + await dbContext.FinancialFreeFormInitiativeTrackingData.AddAsync(new FFFInitiativeTrackingData(initiative.InitiativeGUID), cancellationToken); + } + else + { + await dbContext.FreeFormInitiativeTrackingData.AddAsync(new FFInitiativeTrackingData(initiative.InitiativeGUID), cancellationToken); + } + await dbContext.SaveChangesAsync(cancellationToken); + + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(initiative.InitiativeGUID, SecurableEntityType.Initiative, cancellationToken); + await _authorizationServiceClient.AddPermissionAsync(initiative.InitiativeGUID, initiative.OwnerGUID, true, false, false, cancellationToken); + + opportunity.InitiativeGuid = initiative.InitiativeGUID; + await dbContext.SaveChangesAsync(cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + return opportunity.InitiativeGuid; + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, opportunityGuid, initiative.InitiativeGUID); + return Guid.Empty; + } + } + + public async Task UpdateAsync(Initiative updatedInitiative, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunity = await dbContext.FFOpportunities + .SingleOrDefaultAsync(opp => opp.InitiativeGuid == updatedInitiative.InitiativeGUID); + var initiative = await dbContext.Initiatives + .SingleOrDefaultAsync(init => init.InitiativeGUID == updatedInitiative.InitiativeGUID, cancellationToken); + if (opportunity.MetricType == Opportunities.MetricType.Financial) + { + var finTrackingData = await dbContext.FinancialFreeFormInitiativeTrackingData + .SingleOrDefaultAsync(opp => opp.InitiativeGUID == updatedInitiative.InitiativeGUID); + if (finTrackingData == null) + { + await dbContext.FinancialFreeFormInitiativeTrackingData.AddAsync(new FFFInitiativeTrackingData(initiative.InitiativeGUID), cancellationToken); + } + } + else + { + var trackingData = await dbContext.FreeFormInitiativeTrackingData + .SingleOrDefaultAsync(opp => opp.InitiativeGUID == updatedInitiative.InitiativeGUID); + if (trackingData == null) + { + await dbContext.FreeFormInitiativeTrackingData.AddAsync(new FFInitiativeTrackingData(initiative.InitiativeGUID), cancellationToken); + } + } + initiative.Update(updatedInitiative); + try + { + await dbContext.SaveChangesAsync(cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + } + catch (Exception ex) + { + throw; + } + return initiative; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FFInitiativeTrackingData.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FFInitiativeTrackingData.cs new file mode 100644 index 0000000..3ce3b96 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FFInitiativeTrackingData.cs @@ -0,0 +1,101 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Initiatives +{ + [Table("FreeFormInitiativeTrackingData", Schema = "cci")] + public class FFInitiativeTrackingData + { + [Key] + public Guid InitiativeGUID { get; set; } + [Precision(15, 4)] + public decimal Month01Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month02Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month03Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month04Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month05Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month06Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month07Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month08Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month09Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month10Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month11Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month12Numerator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month01Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month02Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month03Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month04Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month05Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month06Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month07Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month08Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month09Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month10Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month11Denominator { get; set; } = 0; + [Precision(15, 4)] + public decimal Month12Denominator { get; set; } = 0; + + public FFInitiativeTrackingData(Guid initiativeGUID, decimal month01Numerator, decimal month02Numerator, decimal month03Numerator, + decimal month04Numerator, decimal month05Numerator, decimal month06Numerator, decimal month07Numerator, decimal month08Numerator, + decimal month09Numerator, decimal month10Numerator, decimal month11Numerator, decimal month12Numerator, decimal month01Denominator, + decimal month02Denominator, decimal month03Denominator, decimal month04Denominator, decimal month05Denominator, + decimal month06Denominator, decimal month07Denominator, decimal month08Denominator, decimal month09Denominator, + decimal month10Denominator, decimal month11Denominator, decimal month12Denominator) + { + this.InitiativeGUID = initiativeGUID; + this.Month01Numerator = month01Numerator; + this.Month02Numerator = month02Numerator; + this.Month03Numerator = month03Numerator; + this.Month04Numerator = month04Numerator; + this.Month05Numerator = month05Numerator; + this.Month06Numerator = month06Numerator; + this.Month07Numerator = month07Numerator; + this.Month08Numerator = month08Numerator; + this.Month09Numerator = month09Numerator; + this.Month10Numerator = month10Numerator; + this.Month11Numerator = month11Numerator; + this.Month12Numerator = month12Numerator; + this.Month01Denominator = month01Denominator; + this.Month02Denominator = month02Denominator; + this.Month03Denominator = month03Denominator; + this.Month04Denominator = month04Denominator; + this.Month05Denominator = month05Denominator; + this.Month06Denominator = month06Denominator; + this.Month07Denominator = month07Denominator; + this.Month08Denominator = month08Denominator; + this.Month09Denominator = month09Denominator; + this.Month10Denominator = month10Denominator; + this.Month11Denominator = month11Denominator; + this.Month12Denominator = month12Denominator; + } + + public FFInitiativeTrackingData(Guid initiativeGUID) + { + InitiativeGUID = initiativeGUID; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FreeFormInitiativeDto.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FreeFormInitiativeDto.cs new file mode 100644 index 0000000..57afb4f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FreeFormInitiativeDto.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; +using System; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Initiatives +{ + public class FreeFormInitiativeDto : InitiativeDto + { + public Guid ConfigurationGuid { get; set; } + public int ImprovementType { get; set; } + public int MetricType { get; set; } + public string MetricNumerator { get; set; } + public string MetricDenominator { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FreeFormInitiativeMapper.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FreeFormInitiativeMapper.cs new file mode 100644 index 0000000..a94d132 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/FreeFormInitiativeMapper.cs @@ -0,0 +1,94 @@ +using Newtonsoft.Json; +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Initiatives +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class FreeFormInitiativeMapper + { + public static FreeFormInitiativeDto MapToFreeFormInitiative(this Initiative src, FFOpportunityDataModel opportunity) + { + var mapped = src.Map(); + if (mapped == null) return mapped; + var milestones = JsonConvert.DeserializeObject(src.MilestonesConfigJSON); + var idx = 1; + foreach (var milestone in milestones.Milestones) + { + if (DateTime.TryParse(milestone.DueDate, out var dueDate)) + { + mapped.TrySetPropertyValue($"DueDate{idx}", dueDate.ToString("yyyy-MM-dd"), true); + } + mapped.TrySetPropertyValue($"Milestone{idx}", milestone.Name, true); + idx += 1; + } + mapped.IsFinalized = src.CurrentStepEnum == InitiativeSteps.Tracking; + var stakeholders = JsonConvert.DeserializeObject>(src.StakeholdersJSON); + mapped.Stakeholders = stakeholders.Select(sh => sh.UserGUID.ToString()).ToArray(); + mapped.TrackingStart = src.EffectiveDate.ToString("yyyy-MM-dd"); + mapped.OpportunityGuid = opportunity.OpportunityGuid; + mapped.Name = opportunity.Name; + mapped.ImprovementType = (int)opportunity.FinancialImprovementType; + mapped.MetricType = (int)opportunity.MetricType; + mapped.MetricNumerator = opportunity.Numerator; + mapped.MetricDenominator = opportunity.Denominator; + return mapped; + } + + [MapProperty(nameof(FreeFormInitiativeDto.MetricNumerator), nameof(FFOpportunityDataModel.Numerator))] + [MapProperty(nameof(FreeFormInitiativeDto.MetricDenominator), nameof(FFOpportunityDataModel.Denominator))] + [MapProperty(nameof(FreeFormInitiativeDto.ImprovementType), nameof(FFOpportunityDataModel.FinancialImprovementType))] + public static partial FFOpportunityDataModel MapToOpportunity(this FreeFormInitiativeDto src); + + [MapProperty(nameof(Initiative.InitiativeGUID), nameof(FreeFormInitiativeDto.InitiativeGuid))] + [MapProperty(nameof(Initiative.ConfigurationGUID), nameof(FreeFormInitiativeDto.ConfigurationGuid))] + [MapProperty(nameof(Initiative.CostLeaderGUID), nameof(FreeFormInitiativeDto.CostLeader))] + [MapProperty(nameof(Initiative.ExecutiveSponsorGUID), nameof(FreeFormInitiativeDto.ExecutiveSponsor))] + [MapProperty(nameof(Initiative.OwnerGUID), nameof(FreeFormInitiativeDto.Owner))] + [MapProperty(nameof(Initiative.Description), nameof(FreeFormInitiativeDto.PlannedImprovements))] + [MapperIgnoreSource(nameof(Initiative.EffectiveDate))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.IsFinalized))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.ImprovementType))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.MetricType))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.MetricNumerator))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Stakeholders))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.TrackingStart))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone1))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone2))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone3))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone4))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone5))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone6))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone7))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone8))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone9))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.Milestone10))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate1))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate2))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate3))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate4))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate5))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate6))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate7))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate8))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate9))] + [MapperIgnoreTarget(nameof(FreeFormInitiativeDto.DueDate10))] + private static partial FreeFormInitiativeDto Map(this Initiative src); + } + + public class MilestonesDto + { + public List Milestones { get; set; } + + public MilestonesDto() + { + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/IFFInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/IFFInitiativeService.cs new file mode 100644 index 0000000..327b124 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Initiatives/IFFInitiativeService.cs @@ -0,0 +1,13 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Initiatives +{ + public interface IFFInitiativeService + { + Task CreateInitiative(Guid dbGuid, Initiative initiative, Guid opportunityGuid, CancellationToken cancellationToken); + Task UpdateAsync(Initiative updatedInitiative, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunity.cs new file mode 100644 index 0000000..1809a19 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunity.cs @@ -0,0 +1,39 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Opportunities +{ + [Table("viewFreeFormOpportunity", Schema = "cci")] + public class FFOpportunity : Opportunity + { + public FinancialImprovementType ImprovementType { get; set; } + public MetricType MetricType { get; set; } + public string MetricTypeValue { get; set; } + public long OpportunityId { get; set; } + public string Name { get; set; } + + public override void Update(IOpportunity updates) + { + base.Update(updates); + var ffUpdates = updates as FFOpportunity; + if (ffUpdates == null) return; + ImprovementType = ffUpdates.ImprovementType; + MetricType = ffUpdates.MetricType; + MetricTypeValue = ffUpdates.MetricTypeValue; + Name = ffUpdates.Name; + } + } + + public enum MetricType : byte + { + Operational = 0, + FinancialPerUnit = 1, + Financial = 2 + } + + public enum FinancialImprovementType : byte + { + Cost = 0, + Revenue = 1 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityDataModel.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityDataModel.cs new file mode 100644 index 0000000..1fcaeab --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityDataModel.cs @@ -0,0 +1,144 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Network; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Opportunities +{ + [Table("FreeFormOpportunity", Schema = "cci")] + public class FFOpportunityDataModel : IOpportunity, IWithAuthor + { + public FFOpportunityDataModel() + { + OpportunityGuid = Guid.NewGuid(); + OpportunityType = (byte)OpportunityTypes.FreeForm; + OpportunityKey = $"FF - {OpportunityId}"; + Initiative = InitiativeObj?.Name ?? ""; + DateCreated = DateTime.Now; + Note = string.Empty; + } + + [Key] + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public string Numerator { get; set; } + public string Denominator { get; set; } + public FinancialImprovementType FinancialImprovementType { get; set; } + public DateTime DateCreated { get; set; } + public Guid AuthorGuid { get; set; } + public string AuthorName { get; set; } + public byte OpportunityType { get; set; } + public MetricType MetricType { get; set; } + [Precision(15, 4)] + public decimal SavingsPerUnit { get; set; } + public bool IsHidden { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long OpportunityId { get; set; } + [Precision(15, 4)] + public decimal GoalRate { get; set; } + [Precision(15, 4)] + public decimal ChangeRate { get; set; } + public double Savings { get; set; } + [Precision(15, 4)] + public decimal Month01Upper { get; set; } + [Precision(15, 4)] + public decimal Month02Upper { get; set; } + [Precision(15, 4)] + public decimal Month03Upper { get; set; } + [Precision(15, 4)] + public decimal Month04Upper { get; set; } + [Precision(15, 4)] + public decimal Month05Upper { get; set; } + [Precision(15, 4)] + public decimal Month06Upper { get; set; } + [Precision(15, 4)] + public decimal Month07Upper { get; set; } + [Precision(15, 4)] + public decimal Month08Upper { get; set; } + [Precision(15, 4)] + public decimal Month09Upper { get; set; } + [Precision(15, 4)] + public decimal Month10Upper { get; set; } + [Precision(15, 4)] + public decimal Month11Upper { get; set; } + [Precision(15, 4)] + public decimal Month12Upper { get; set; } + [Precision(15, 4)] + public decimal Month01Lower { get; set; } + [Precision(15, 4)] + public decimal Month02Lower { get; set; } + [Precision(15, 4)] + public decimal Month03Lower { get; set; } + [Precision(15, 4)] + public decimal Month04Lower { get; set; } + [Precision(15, 4)] + public decimal Month05Lower { get; set; } + [Precision(15, 4)] + public decimal Month06Lower { get; set; } + [Precision(15, 4)] + public decimal Month07Lower { get; set; } + [Precision(15, 4)] + public decimal Month08Lower { get; set; } + [Precision(15, 4)] + public decimal Month09Lower { get; set; } + [Precision(15, 4)] + public decimal Month10Lower { get; set; } + [Precision(15, 4)] + public decimal Month11Lower { get; set; } + [Precision(15, 4)] + public decimal Month12Lower { get; set; } + [NotMapped] + public virtual Configuration.Configuration Configuration { get; set; } + + #region IWithSourceOpportunity + [NotMapped] + public string SourceOpportunity { get; set; } + [NotMapped] + public long SourceOpportunityId { get; set; } + public void SetSourceOpportunity(ClientNetworkOpportunity sourceOpportunity) + { + if (sourceOpportunity == null) { return; } + SourceOpportunity = sourceOpportunity.NetworkOpportunity.Name; + SourceOpportunityId = sourceOpportunity.NetworkOpportunityId; + } + #endregion + + #region IOpportunity items + [NotMapped] + public string OpportunityKey { get; set; } + [NotMapped] + public double IdentifiedSavings { get; set; } + [NotMapped] + public bool HasInitiative => InitiativeGuid != Guid.Empty; + [NotMapped] + public string Initiative { get; set; } + [NotMapped] + public string Note { get; set; } + #endregion + + [NotMapped] + public virtual Initiative InitiativeObj { get; set; } + + public void Update(IOpportunity updates) + { + IdentifiedSavings = updates.IdentifiedSavings; + InitiativeGuid = updates.InitiativeGuid; + IsHidden = updates.IsHidden; + Note = updates.Note; + var ffUpdates = updates as FFOpportunityDataModel; + if (ffUpdates == null) return; + Name = ffUpdates.Name; + FinancialImprovementType = ffUpdates.FinancialImprovementType; + MetricType = ffUpdates.MetricType; + Numerator = ffUpdates.Numerator; + Denominator = ffUpdates.Denominator; + } + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityDataModelMapper.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityDataModelMapper.cs new file mode 100644 index 0000000..00aa86c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityDataModelMapper.cs @@ -0,0 +1,83 @@ +using MoreLinq; +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Utilities; +using System.Linq; +using System.Security.Claims; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Opportunities +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class FFOpportunityDataModelMapper + { + public static FFOpportunityDataModel MapToDataModel(this FreeFormInitiativeDto src, ClaimsPrincipal user) + { + var mapped = src.Mapped(); + if (mapped == null) return default; + + mapped.MapAuthor(user); + mapped.Numerator ??= "Baseline"; + mapped.Denominator ??= "Target"; + return mapped; + } + + public static FFOpportunityDetailModel MapToDetailModel(this FFOpportunityDataModel src, IFiscalMonthResolver fiscalMonthResolver) + { + var mapped = MappedToDetailModel(src); + if (mapped == null) return default; + + mapped.OpportunityKey = "FF - " + src.OpportunityId; + mapped.BaselineStartDate = src.Configuration.GetBaselineStartDate(OpportunityTypes.OrgDefined); + mapped.BaselineEndDate = src.Configuration.GetBaselineEndDate(OpportunityTypes.OrgDefined); + mapped.TrackingStartDate = src.Configuration.GetTrackingStartDate(OpportunityTypes.OrgDefined); + mapped.TrackingEndDate = src.Configuration.GetTrackingEndDate(); + mapped.Numerator = string.IsNullOrEmpty(mapped.Numerator) ? "Baseline" : mapped.Numerator; + mapped.Denominator = string.IsNullOrEmpty(mapped.Denominator) ? "Target" : mapped.Denominator; + // Get the real Fiscal Months in order and double them in case we need to use the next year. + var realFiscalMonths = fiscalMonthResolver.AllFiscalMonths.Where(x => x.IsReal).OrderBy(x => x.SortOrder).ToList(); + realFiscalMonths.AddRange(fiscalMonthResolver.AllFiscalMonths.Where(x => x.IsReal).OrderBy(x => x.SortOrder)); + var start = realFiscalMonths.Find(fm => fm.FiscalMonthId == mapped.BaselineStartDate.Month); + var fmIndex = realFiscalMonths.IndexOf(start); + + var baselineStart = mapped.BaselineStartDate; + mapped.RowData = realFiscalMonths.Skip(fmIndex).Take(12) + .Select((fm, idx) => new FFOpportunityDetailRowData( + fm.MonthColumnName.ToLower(), + baselineStart.AddMonths(idx), + src.TryGetPropertyValue($"{fm.MonthColumnName}Upper", out decimal upper) ? upper : 0m, + src.TryGetPropertyValue($"{fm.MonthColumnName}Lower", out decimal lower) ? lower : 0m) + ).ToList(); + return mapped; + } + + [MapperIgnoreTarget(nameof(FFOpportunityDataModel.OpportunityGuid))] + [MapProperty(nameof(FFOpportunityDto.ImprovementType), nameof(FFOpportunityDataModel.FinancialImprovementType))] + [MapProperty(nameof(FFOpportunityDto.MetricDenominator), nameof(FFOpportunityDataModel.Denominator))] + [MapProperty(nameof(FFOpportunityDto.MetricNumerator), nameof(FFOpportunityDataModel.Numerator))] + public static FFOpportunityDataModel MapToDataModel(this FFOpportunityDto src) + { + var mapped = OpportunityToDataModel(src); + if (mapped == null) return null; + + return mapped; + } + + [MapperIgnoreTarget(nameof(FFOpportunityDataModel.OpportunityGuid))] + [MapProperty(nameof(FFOpportunityDto.ImprovementType), nameof(FFOpportunityDataModel.FinancialImprovementType))] + [MapProperty(nameof(FFOpportunityDto.MetricDenominator), nameof(FFOpportunityDataModel.Denominator))] + [MapProperty(nameof(FFOpportunityDto.MetricNumerator), nameof(FFOpportunityDataModel.Numerator))] + private static partial FFOpportunityDataModel OpportunityToDataModel(this FFOpportunityDto src); + + [MapperIgnoreTarget(nameof(FFOpportunityDataModel.OpportunityGuid))] + [MapProperty(nameof(FreeFormInitiativeDto.ImprovementType), nameof(FFOpportunityDataModel.FinancialImprovementType))] + [MapProperty(nameof(FreeFormInitiativeDto.MetricDenominator), nameof(FFOpportunityDataModel.Denominator))] + [MapProperty(nameof(FreeFormInitiativeDto.MetricNumerator), nameof(FFOpportunityDataModel.Numerator))] + private static partial FFOpportunityDataModel Mapped(this FreeFormInitiativeDto src); + + private static partial FFOpportunityDetailModel MappedToDetailModel(this FFOpportunityDataModel src); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityDetailModel.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityDetailModel.cs new file mode 100644 index 0000000..89dcfd2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityDetailModel.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Opportunities +{ + public class FFOpportunityDetailModel + { + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public long OpportunityId { get; set; } + public string OpportunityKey { get; set; } + public string Name { get; set; } + public string Numerator { get; set; } + public string Denominator { get; set; } + public float Savings { get; set; } + public float SavingsPerUnit { get; set; } + public float GoalRate { get; set; } + public float ChangeRate { get; set; } + public DateTime BaselineStartDate { get; set; } + public DateTime BaselineEndDate { get; set; } + public DateTime TrackingStartDate { get; set; } + public DateTime TrackingEndDate { get; set; } + public FinancialImprovementType FinancialImprovementType { get; set; } + public MetricType MetricType { get; set; } + public bool IsHidden { get; set; } + public string SourceOpportunity { get; set; } + public long SourceOpportunityId { get; set; } + public List RowData { get; set; } + } + + public class FFOpportunityDetailRowData + { + public string Key { get; set; } + public string Month { get; set; } + public decimal Upper { get; set; } + public decimal Lower { get; set; } + + public FFOpportunityDetailRowData() + { + + } + + public FFOpportunityDetailRowData(string key, DateTime date, decimal upper, decimal lower) + { + Key = key; + Month = $"{date:MMM-yy}"; + Upper = upper; + Lower = lower; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityService.cs new file mode 100644 index 0000000..4fbfe77 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/FFOpportunityService.cs @@ -0,0 +1,228 @@ +using ClosedXML.Excel; +using Microsoft.EntityFrameworkCore; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.Hangfire.Jazz.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Opportunities +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class FFOpportunityService : OpportunityService, IFFOpportunityService + { + private readonly IJazzHangfireServiceClient _hangfireServiceClient; + private readonly IAuthorizationServiceClient _authorizationServiceClient; + private readonly IFiscalMonthResolver _fiscalMonthResolver; + + public FFOpportunityService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IJazzHangfireServiceClient hangfireServiceClient, + IFiscalMonthResolver fiscalMonthResolver, + IInitiativeService initiativeService, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + IAuthorizationServiceClient authorizationServiceClient) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) + { + _hangfireServiceClient = hangfireServiceClient; + _authorizationServiceClient = authorizationServiceClient; + _fiscalMonthResolver = fiscalMonthResolver; + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return null; + + var opportunityContext = dbContext.Set(); + var configuredOpportunities = opportunityContext.Where(ff => ff.ConfigurationGuid == configurationGuid); + + var metricTypes = (await configuredOpportunities.Select(mt => new { mt.MetricType }).Distinct().ToListAsync(cancellationToken)) + .Select(mt => new FilterMember + { + Id = ((int)mt.MetricType).ToString(), + Text = Enum.GetName(mt.MetricType).SplitCamelCase() + }).Distinct().OrderBy(t => t.Text).ToList(); + + var financialImprovementTypes = (await configuredOpportunities.Select(it => new { it.ImprovementType }).Distinct().ToListAsync(cancellationToken)) + .Select(ff => new FilterMember + { + Id = ((int)ff.ImprovementType).ToString(), + Text = Enum.GetName(ff.ImprovementType).SplitCamelCase() + }).Distinct().OrderBy(oa => oa.Text).ToList(); + + return new FFFilterOptions + { + MetricTypes = metricTypes, + FinancialImprovementTypes = financialImprovementTypes + }; + + } + + public async Task CreateAsync(FFOpportunityDataModel opportunity, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + + var configuration = await dbContext.Configurations + .FirstOrDefaultAsync(x => x.ConfigurationGuid == opportunity.ConfigurationGuid, cancellationToken); + if (opportunity.MetricType == MetricType.FinancialPerUnit) opportunity.SavingsPerUnit = 1; + + await dbContext.FreeFormOpportunities.AddAsync(opportunity, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(opportunity.OpportunityGuid, SecurableEntityType.Opportunity, cancellationToken); + + return opportunity.OpportunityGuid; + } + + public async Task UpdateAsync(FFOpportunityDataModel opportunity, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + var ffOpportunity = await dbContext.FreeFormOpportunities + .SingleOrDefaultAsync(ff => ff.OpportunityGuid == opportunity.OpportunityGuid, cancellationToken); + ffOpportunity.Update(opportunity); + await dbContext.SaveChangesAsync(cancellationToken); + return await dbContext.FFOpportunities + .SingleOrDefaultAsync(ff => ff.OpportunityGuid == opportunity.OpportunityGuid, cancellationToken); + } + + public async Task GetDataModelById(Guid opportunityGuid, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + var opportunity = await dbContext.FreeFormOpportunities.FirstOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + opportunity.OpportunityKey = $"FF - {opportunity.OpportunityId}"; + opportunity.Configuration = await dbContext.Configurations.SingleOrDefaultAsync(c => c.ConfigurationGuid == opportunity.ConfigurationGuid, cancellationToken); + if (opportunity.InitiativeGuid != Guid.Empty) + { + opportunity.InitiativeObj = await dbContext.Initiatives.SingleOrDefaultAsync(i => i.InitiativeGUID == opportunity.InitiativeGuid, cancellationToken); + opportunity.Initiative = opportunity.InitiativeObj?.Name ?? ""; + } + var link = await _centralDbContext.ClientNetworkOpportunityLinks + .Include(cno => cno.ClientNetworkOpportunity) + .ThenInclude(no => no.NetworkOpportunity) + .SingleOrDefaultAsync(link => link.OpportunityGuid == opportunityGuid, cancellationToken); + if (link != null) + { + opportunity.SourceOpportunity = link.ClientNetworkOpportunity.NetworkOpportunity.Name; + opportunity.SourceOpportunityId = link.ClientNetworkOpportunity.NetworkOpportunityId; + } + opportunity.Configuration.FiscalMonthResolver = _fiscalMonthResolver; + opportunity = (FFOpportunityDataModel)await SetSourceOpportunity(opportunity, cancellationToken); + return opportunity; + } + + public async Task SetGoalsAsync(Guid opportunityGuid, FFOpportunityDetailModel opportunityDetail, IList monthValues, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + + try + { + //get opportunity + var opportunity = await dbContext.FreeFormOpportunities + .FirstOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunity == null) { return false; } + + //update opportunity fields + opportunity.ChangeRate = (decimal)opportunityDetail.ChangeRate; + opportunity.GoalRate = (decimal)opportunityDetail.GoalRate; + opportunity.SavingsPerUnit = (decimal)opportunityDetail.SavingsPerUnit; + opportunity.Savings = opportunityDetail.Savings; + opportunity.IdentifiedSavings = opportunityDetail.Savings; + + //update monthly values + foreach (var row in monthValues) + { + opportunity.TrySetPropertyValue($"{row.Key}Upper", row.Upper, true); + opportunity.TrySetPropertyValue($"{row.Key}Lower", row.Lower, true); + } + + await dbContext.SaveChangesAsync(); + + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(init => init.InitiativeGUID == opportunity.InitiativeGuid, cancellationToken); + if (initiative == null) { return false; } + + initiative.SavingsCommitted = opportunityDetail.Savings; + await dbContext.SaveChangesAsync(); + + if (!(await RunTracking(opportunity.InitiativeGuid, cancellationToken))) + { + return false; + } + + return true; + } + catch (Exception ex) + { + return false; + } + } + + private async Task RunTracking(Guid initiativeGuid, CancellationToken cancellationToken) + { + try + { + var dbContext = await GetDbContext(cancellationToken); + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(i => i.InitiativeGUID.Equals(initiativeGuid), cancellationToken); + + var jobId = await _initiativeService.RunTracking(initiativeGuid, cancellationToken); + + // Wait for the job to finish + var result = await _hangfireServiceClient.GetJobInfoAsync(jobId, cancellationToken); + while (result.State.Name == "Processing") + { + await Task.Delay(500); + result = await _hangfireServiceClient.GetJobInfoAsync(jobId, cancellationToken); + } + + if (result.State.Name != "Succeeded") { return false; } + + return true; + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + return false; + } + } + + public async Task ExportAsync(SortOptions sortOptions, FFFilters filters, CancellationToken cancellationToken) + { + var exportOptions = this.GetExportOptions(); + + // get data + var data = (await this.GetAllAsync(sortOptions, filters, cancellationToken)).Select(opp => new + { + Opportunity = opp.OpportunityKey, + opp.Name, + ImprovementType = $"{opp.ImprovementType}".SplitCamelCase(), + MetricType = $"{opp.MetricType}".SplitCamelCase(), + GoalSavings = opp.IdentifiedSavings, + opp.Initiative, + IsHidden = opp.IsHidden ? 1 : 0, + opp.Note + }); + + // configure column formats + exportOptions.AddColumnOptions("Goal Savings", "Currency2Dec"); + exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\""); + + return ExportUtils.CreateExcelWorkbook(data, exportOptions); + } + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/Filters/FFFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/Filters/FFFilterOptions.cs new file mode 100644 index 0000000..6aaad9e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/Filters/FFFilterOptions.cs @@ -0,0 +1,11 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Opportunities.Filters +{ + public class FFFilterOptions + { + public IEnumerable FinancialImprovementTypes { get; set; } + public IEnumerable MetricTypes { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/Filters/FFFilters.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/Filters/FFFilters.cs new file mode 100644 index 0000000..ca35e29 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/Filters/FFFilters.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Opportunities.Filters +{ + public class FFFilters : FiltersOpportunity + { + public IEnumerable FinancialImprovementTypes { get; set; } + public IEnumerable MetricTypes { get; set; } + + public FFFilters() : base() + { + FinancialImprovementTypes = new List(); + MetricTypes = new List(); + } + + public override IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = base.GetFilters(context).ToList(); + + if (FinancialImprovementTypes.Any()) + filters.Add(m => FinancialImprovementTypes.Contains(((int)m.ImprovementType).ToString())); + + if (MetricTypes.Any()) + filters.Add(m => MetricTypes.Contains(((int)m.MetricType).ToString())); + + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + var matchedImprovementTypeName = Enum.GetNames(typeof(FinancialImprovementType)).FirstOrDefault(fit => fit.ToLower().Contains(OpportunitySearch.ToLower())); + var testImprovementType = Enum.TryParse(matchedImprovementTypeName, true, out var financialImprovementType); + filters.Add(m => m.OpportunityKey.Contains(OpportunitySearch) + || m.Name.Contains(OpportunitySearch) + || (testImprovementType && m.ImprovementType == financialImprovementType) + || m.MetricTypeValue.Contains(OpportunitySearch)); + } + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/IFFOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/IFFOpportunityService.cs new file mode 100644 index 0000000..efa9981 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/FreeForm/Opportunities/IFFOpportunityService.cs @@ -0,0 +1,28 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.FreeForm.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.FreeForm.Opportunities +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public interface IFFOpportunityService : IOpportunityService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + + Task ExportAsync(SortOptions sortOptions, FFFilters filters, CancellationToken cancellationToken); + + Task CreateAsync(FFOpportunityDataModel opportunity, CancellationToken cancellationToken); + + Task UpdateAsync(FFOpportunityDataModel opportunity, CancellationToken cancellationToken); + + Task SetGoalsAsync(Guid opportunityGuid, FFOpportunityDetailModel opportunityDetail, IList monthValues, CancellationToken cancellationToken); + + Task GetDataModelById(Guid opportunityGuid, CancellationToken cancellationToken); + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/DatadogSection.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/DatadogSection.cs new file mode 100644 index 0000000..642c61e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/DatadogSection.cs @@ -0,0 +1,7 @@ +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public class DatadogSection + { + public string SeedGMDNJob { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/DeviceIdentifierDimension.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/DeviceIdentifierDimension.cs new file mode 100644 index 0000000..2efb247 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/DeviceIdentifierDimension.cs @@ -0,0 +1,144 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + /// + /// Dimension consists of aggregated data from GMDN & GUDID data sources + /// + public class DeviceIdentifierDimension + { + public string DeviceIdentifier { get; set; } + public string PrimaryDI { get; set; } + public string DeviceIdType { get; set; } + public string GMDNTermCode { get; set; } + public string GMDNTermName { get; set; } + public string GMDNTermDefinition { get; set; } + public string GMDNDeviceFunction1 { get; set; } = ""; + public string GMDNDeviceFunction2 { get; set; } = ""; + public string GMDNDeviceFunction3 { get; set; } = ""; + public string GMDNDeviceFunction4 { get; set; } = ""; + public string GMDNDeviceFunction5 { get; set; } = ""; + public string GMDNDeviceFunction6 { get; set; } = ""; + public string GMDNDeviceFunction7 { get; set; } = ""; + public string GMDNInvasiveness1 { get; set; } = ""; + public string GMDNInvasiveness2 { get; set; } = ""; + public string GMDNUseFrequency1 { get; set; } = ""; + public string GMDNUseFrequency2 { get; set; } = ""; + public string GMDNPower1 { get; set; } = ""; + public string GMDNPower2 { get; set; } = ""; + public string GMDNMaterials1 { get; set; } = ""; + public string GMDNMaterials2 { get; set; } = ""; + public string GMDNFeaturedAttributes1 { get; set; } = ""; + public string GMDNFeaturedAttributes2 { get; set; } = ""; + public string GMDNAnatomicalSpecialty1 { get; set; } = ""; + public string GMDNAnatomicalSpecialty2 { get; set; } = ""; + public string GMDNAnatomicalSpecialty3 { get; set; } = ""; + public string GMDNAnatomicalSpecialty4 { get; set; } = ""; + public string GMDNAnatomicalSpecialty5 { get; set; } = ""; + public string GMDNAnatomicalSpecialty6 { get; set; } = ""; + public string GMDNNamesIndex1 { get; set; } = ""; + public string GMDNNamesIndex2 { get; set; } = ""; + public string GMDNNamesIndex3 { get; set; } = ""; + public string GMDNNamesIndex4 { get; set; } = ""; + public string GMDNNamesIndex5 { get; set; } = ""; + public string GMDNNamesIndex6 { get; set; } = ""; + public string GMDNNamesIndex7 { get; set; } = ""; + public string GUDIDCompanyName { get; set; } = ""; + + public bool HasChanges(DeviceIdentifierDimension dim) + { + if (PrimaryDI != dim.PrimaryDI + || DeviceIdType != dim.DeviceIdType + || GMDNTermCode != dim.GMDNTermCode + || GMDNTermName != dim.GMDNTermName + || GMDNTermDefinition != dim.GMDNTermDefinition + || GMDNDeviceFunction1 != dim.GMDNDeviceFunction1 + || GMDNDeviceFunction2 != dim.GMDNDeviceFunction2 + || GMDNDeviceFunction3 != dim.GMDNDeviceFunction3 + || GMDNDeviceFunction4 != dim.GMDNDeviceFunction4 + || GMDNDeviceFunction5 != dim.GMDNDeviceFunction5 + || GMDNDeviceFunction6 != dim.GMDNDeviceFunction6 + || GMDNDeviceFunction7 != dim.GMDNDeviceFunction7 + || GMDNInvasiveness1 != dim.GMDNInvasiveness1 + || GMDNInvasiveness2 != dim.GMDNInvasiveness2 + || GMDNUseFrequency1 != dim.GMDNUseFrequency1 + || GMDNUseFrequency2 != dim.GMDNUseFrequency2 + || GMDNPower1 != dim.GMDNPower1 + || GMDNPower2 != dim.GMDNPower2 + || GMDNMaterials1 != dim.GMDNMaterials1 + || GMDNMaterials2 != dim.GMDNMaterials2 + || GMDNFeaturedAttributes1 != dim.GMDNFeaturedAttributes1 + || GMDNFeaturedAttributes2 != dim.GMDNFeaturedAttributes2 + || GMDNAnatomicalSpecialty1 != dim.GMDNAnatomicalSpecialty1 + || GMDNAnatomicalSpecialty2 != dim.GMDNAnatomicalSpecialty2 + || GMDNAnatomicalSpecialty3 != dim.GMDNAnatomicalSpecialty3 + || GMDNAnatomicalSpecialty4 != dim.GMDNAnatomicalSpecialty4 + || GMDNAnatomicalSpecialty5 != dim.GMDNAnatomicalSpecialty5 + || GMDNAnatomicalSpecialty6 != dim.GMDNAnatomicalSpecialty6 + || GMDNNamesIndex1 != dim.GMDNNamesIndex1 + || GMDNNamesIndex2 != dim.GMDNNamesIndex2 + || GMDNNamesIndex3 != dim.GMDNNamesIndex3 + || GMDNNamesIndex4 != dim.GMDNNamesIndex4 + || GMDNNamesIndex5 != dim.GMDNNamesIndex5 + || GMDNNamesIndex6 != dim.GMDNNamesIndex6 + || GMDNNamesIndex7 != dim.GMDNNamesIndex7 + || GUDIDCompanyName != dim.GUDIDCompanyName) + { + return true; + } + return false; + } + } + + [Table("DimDeviceIdentifier", Schema = "dss")] + public class DimDeviceIdentifier : DeviceIdentifierDimension + { + [DatabaseGenerated(DatabaseGeneratedOption.Computed)] + public Guid MemberGUID { get; set; } + public Guid HistoryItemGUID { get; set; } + public int TransactionID { get; set; } + + public DimDeviceIdentifier Update(DeviceIdentifierDimension dim) + { + PrimaryDI = dim.PrimaryDI; + DeviceIdType = dim.DeviceIdType; + GMDNTermCode = dim.GMDNTermCode; + GMDNTermName = dim.GMDNTermName; + GMDNTermDefinition = dim.GMDNTermDefinition; + GMDNDeviceFunction1 = dim.GMDNDeviceFunction1; + GMDNDeviceFunction2 = dim.GMDNDeviceFunction2; + GMDNDeviceFunction3 = dim.GMDNDeviceFunction3; + GMDNDeviceFunction4 = dim.GMDNDeviceFunction4; + GMDNDeviceFunction5 = dim.GMDNDeviceFunction5; + GMDNDeviceFunction6 = dim.GMDNDeviceFunction6; + GMDNDeviceFunction7 = dim.GMDNDeviceFunction7; + GMDNInvasiveness1 = dim.GMDNInvasiveness1; + GMDNInvasiveness2 = dim.GMDNInvasiveness2; + GMDNUseFrequency1 = dim.GMDNUseFrequency1; + GMDNUseFrequency2 = dim.GMDNUseFrequency2; + GMDNPower1 = dim.GMDNPower1; + GMDNPower2 = dim.GMDNPower2; + GMDNMaterials1 = dim.GMDNMaterials1; + GMDNMaterials2 = dim.GMDNMaterials2; + GMDNFeaturedAttributes1 = dim.GMDNFeaturedAttributes1; + GMDNFeaturedAttributes2 = dim.GMDNFeaturedAttributes2; + GMDNAnatomicalSpecialty1 = dim.GMDNAnatomicalSpecialty1; + GMDNAnatomicalSpecialty2 = dim.GMDNAnatomicalSpecialty2; + GMDNAnatomicalSpecialty3 = dim.GMDNAnatomicalSpecialty3; + GMDNAnatomicalSpecialty4 = dim.GMDNAnatomicalSpecialty4; + GMDNAnatomicalSpecialty5 = dim.GMDNAnatomicalSpecialty5; + GMDNAnatomicalSpecialty6 = dim.GMDNAnatomicalSpecialty6; + GMDNNamesIndex1 = dim.GMDNNamesIndex1; + GMDNNamesIndex2 = dim.GMDNNamesIndex2; + GMDNNamesIndex3 = dim.GMDNNamesIndex3; + GMDNNamesIndex4 = dim.GMDNNamesIndex4; + GMDNNamesIndex5 = dim.GMDNNamesIndex5; + GMDNNamesIndex6 = dim.GMDNNamesIndex6; + GMDNNamesIndex7 = dim.GMDNNamesIndex7; + GUDIDCompanyName = dim.GUDIDCompanyName; + return this; + } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/DeviceIdentifierTerm.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/DeviceIdentifierTerm.cs new file mode 100644 index 0000000..8925841 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/DeviceIdentifierTerm.cs @@ -0,0 +1,12 @@ +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public class DeviceIdentifierTerm + { + public string DeviceId { get; set; } + public string DeviceIdType { get; set; } + public string PrimaryDI { get; set; } + public string CompanyName { get; set; } + public string GMDNCode { get; set; } + public int RowNumber { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/DimDeviceIdentifierMapper.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/DimDeviceIdentifierMapper.cs new file mode 100644 index 0000000..e88291b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/DimDeviceIdentifierMapper.cs @@ -0,0 +1,19 @@ +using Riok.Mapperly.Abstractions; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class DimDeviceIdentifierMapper + { + public static DimDeviceIdentifier MapToJazzModel(this DeviceIdentifierDimension src) + { + var mapped = Mapped(src); + if (mapped == null) { return default; } + return mapped; + } + + private static partial DimDeviceIdentifier Mapped(DeviceIdentifierDimension src); + + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/DimSupply.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/DimSupply.cs new file mode 100644 index 0000000..251d317 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/DimSupply.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + [Table("DimSupply", Schema = "dss")] + public class DimSupply + { + [Key] + public int SupplyID { get; set; } + public string DeviceIdentifier { get; set; } + public string Name { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNCategory.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNCategory.cs new file mode 100644 index 0000000..d438fb4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNCategory.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; +using System.Xml.Serialization; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public class GMDNCategory + { + [Key] + [XmlElement("nodeID")] + public int NodeId { get; set; } + [XmlElement("path")] + public string Path { get; set; } + [XmlElement("categoryCode")] + public string CategoryCode { get; set; } + [XmlElement("name")] + public string Name { get; set; } + [XmlElement("definition")] + public string Definition { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNService.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNService.cs new file mode 100644 index 0000000..2583ff9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNService.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Notification; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public class GMDNService : IGMDNService + { + private readonly CentralDbContext _centralDbContext; + private readonly IHubContext _notificationHubContext; + + public GMDNService(CentralDbContext centralDbContext, IHubContext notificationHubContext) + { + _centralDbContext = centralDbContext; + _notificationHubContext = notificationHubContext; + } + + public class GMDNTermsFile + { + [XmlElement("term")] + public List terms { get; set; } + } + public class GMDNTermCategoriesFile + { + [XmlElement("categoryTermLink")] + public List termCategories { get; set; } + } + public class GMDNCategoriesFile + { + [XmlElement("category")] + public List categories { get; set; } + + } + + public async Task SeedGMDNTerms(IFormFile gmdnTermFile, string userName, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(userName + gmdnTermFile.FileName); + + var serializer = new XmlSerializer(typeof(GMDNTermsFile), new XmlRootAttribute("gmdn")); + var terms = new GMDNTermsFile(); + + using (var reader = gmdnTermFile.OpenReadStream()) + { + terms = (GMDNTermsFile)serializer.Deserialize(reader); + } + + _centralDbContext.Database.ExecuteSqlRaw("TRUNCATE TABLE public.gmdn_terms RESTART IDENTITY;"); + await _centralDbContext.GMDNTerms.AddRangeAsync(terms.terms); + var result = _centralDbContext.SaveChanges(); + + if (result > 0) + { + await hub.SendUploadFileFinish(userName, gmdnTermFile.FileName); + } + else + { + await hub.SendUploadFileError(userName, gmdnTermFile.FileName); + } + } + + public async Task SeedGMDNTermCategories(IFormFile gmdnTermCategoryFile, string userName, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(userName + gmdnTermCategoryFile.FileName); + + var serializer = new XmlSerializer(typeof(GMDNTermCategoriesFile), new XmlRootAttribute("gmdn")); + var termCategoryFile = new GMDNTermCategoriesFile(); + + using (var reader = gmdnTermCategoryFile.OpenReadStream()) + { + termCategoryFile = (GMDNTermCategoriesFile)serializer.Deserialize(reader); + } + + _centralDbContext.Database.ExecuteSqlRaw("TRUNCATE TABLE public.gmdn_term_categories RESTART IDENTITY;"); + await _centralDbContext.GMDNTermCategories.AddRangeAsync(termCategoryFile.termCategories); + var result = _centralDbContext.SaveChanges(); + + if (result > 0) + { + await hub.SendUploadFileFinish(userName, gmdnTermCategoryFile.FileName); + } + else + { + await hub.SendUploadFileError(userName, gmdnTermCategoryFile.FileName); + }; + } + + public async Task SeedGMDNCategories(IFormFile gmdnTermCategoryFile, string userName, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(userName + gmdnTermCategoryFile.FileName); + + var serializer = new XmlSerializer(typeof(GMDNCategoriesFile), new XmlRootAttribute("gmdn")); + var categoryFile = new GMDNCategoriesFile(); + + using (var reader = gmdnTermCategoryFile.OpenReadStream()) + { + categoryFile = (GMDNCategoriesFile)serializer.Deserialize(reader); + } + + _centralDbContext.Database.ExecuteSqlRaw("TRUNCATE TABLE public.gmdn_categories RESTART IDENTITY;"); + await _centralDbContext.GMDNCategories.AddRangeAsync(categoryFile.categories); + var result = _centralDbContext.SaveChanges(); + + if (result > 0) + { + await hub.SendUploadFileFinish(userName, gmdnTermCategoryFile.FileName); + } + else + { + await hub.SendUploadFileError(userName, gmdnTermCategoryFile.FileName); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNTerm.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNTerm.cs new file mode 100644 index 0000000..5995d9b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNTerm.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using System.Xml.Serialization; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + + public class GMDNTerm + { + [Key] + public int TermId { get; set; } + [XmlElement("termCode")] + public string TermCode { get; set; } + [XmlElement("termName")] + public string TermName { get; set; } + [XmlElement("termDefinition")] + public string TermDefinition { get; set; } + [XmlElement("termStatus")] + public string TermStatus { get; set; } + [XmlElement("termIsIVD")] + public string TermIsIVD { get; set; } + [XmlElement("createdDate")] + public string CreatedDate { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNTermCategory.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNTermCategory.cs new file mode 100644 index 0000000..c9634b8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNTermCategory.cs @@ -0,0 +1,12 @@ +using System.Xml.Serialization; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public class GMDNTermCategory + { + [XmlElement("termCode")] + public string TermCode { get; set; } + [XmlElement("categoryCode")] + public string CategoryCode { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNTermExtensions.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNTermExtensions.cs new file mode 100644 index 0000000..5584083 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GMDNTermExtensions.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public static class GMDNTermExtensions + { + public static GUDIDTerm GMDNTerm(this string line) + { + var items = line.Split('|'); + if (items.Length == 6) + return new GUDIDTerm() + { + PrimaryDI = items[0], + GMDNPTName = items[1].ReplaceSpecialCharacters(), + GMDNPTDefinition = items[2], + GMDNCode = items[3], + GMDNCodeStatus = items[4] == "Active" ? GMDNCodeStatus.Active : GMDNCodeStatus.Obsolete, + Implantable = items[5].ToLower() == "true" + }; + + return null; + } + + public static Dictionary KeyValuePairs(this GUDIDTerm term) + => new() + { + {"primaryDI", term.PrimaryDI }, + {"gmdnPTName", term.GMDNPTName}, + {"gmdnPTDefinition", term.GMDNPTDefinition } + }; + + internal static string ReplaceSpecialCharacters(this string value) + { + return value.ReplaceCharacters("[,'`\\\\*?$!={}<>()\\[\\]\"]+", string.Empty) + .ReplaceCharacters("[:/]+", "-") + .ReplaceCharacters("[&]+", "and"); + } + + internal static string ReplaceCharacters(this string value, string pattern, string replacement) + => Regex.Replace(value, pattern, replacement); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDDevice.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDDevice.cs new file mode 100644 index 0000000..ba32589 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDDevice.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics.CodeAnalysis; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + [Index(nameof(PrimaryDi))] + public class GUDIDDevice + { + [Key, MaxLength(24), NotNull] + public string PrimaryDi { get; set; } + [MaxLength(50), NotNull] + public string PublicDeviceRecordKey { get; set; } + [MaxLength(12)] + public string PublicVersionStatus { get; set; } + [MaxLength(12)] + public string DeviceRecordStatus { get; set; } + public int? PublicVersionNumber { get; set; } + public DateTime? PublicVersionDate { get; set; } + public DateTime? DevicePublishDate { get; set; } + public DateTime? DeviceCommDistributionEndDate { get; set; } + [MaxLength(200)] + public string DeviceCommDistributionStatus { get; set; } + [MaxLength(80)] + public string BrandName { get; set; } + [MaxLength(80)] + public string VersionModelNumber { get; set; } + [MaxLength(80)] + public string CatalogNumber { get; set; } + [MaxLength(16)] + public string DunsNumber { get; set; } + [MaxLength(120)] + public string CompanyName { get; set; } + public int? DeviceCount { get; set; } + [MaxLength(2000)] + public string DeviceDescription { get; set; } + public bool DmExempt { get; set; } + public bool PremarketExempt { get; set; } + public bool DeviceHctp { get; set; } + public bool DeviceKit { get; set; } + public bool DeviceCombinationProduct { get; set; } + public bool SingleUse { get; set; } + public bool LotBatch { get; set; } + public bool SerialNumber { get; set; } + public bool ManufacturingDate { get; set; } + public bool ExpirationDate { get; set; } + public bool DonationIdNumber { get; set; } + public bool LabeledContainsNrl { get; set; } + public bool LabeledNoNrl { get; set; } + [MaxLength(50)] + public string MriSafetyStatus { get; set; } + public bool Rx { get; set; } + public bool Otc { get; set; } + public bool DeviceSterile { get; set; } + public bool SterilizationPriorToUse { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDDeviceExtensions.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDDeviceExtensions.cs new file mode 100644 index 0000000..3256bbb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDDeviceExtensions.cs @@ -0,0 +1,60 @@ +using Strata.ContinuousImprovement.Biz.Utilities; +using System; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public static class GUDIDDeviceExtensions + { + public static string TrueFalse(this string aString) => aString.ToLower() == "true" ? "true" : "false"; + + public static GUDIDDevice GUDIDDevice(this string line) + { + var items = line.Split('|'); + if (items.Length != 34) { return null; } + try + { + return new GUDIDDevice() + { + PrimaryDi = items[0], + PublicDeviceRecordKey = items[1], + PublicVersionStatus = items[2], + DeviceRecordStatus = items[3], + PublicVersionNumber = int.TryParse(items[4], out var result4) ? result4 : null, + PublicVersionDate = DateTime.TryParse(items[5], out var result) ? result.ToUniversalTime() : null, + DevicePublishDate = DateTime.TryParse(items[6], out var result1) ? result.ToUniversalTime() : null, + DeviceCommDistributionEndDate = DateTime.TryParse(items[7], out var result2) ? result2.ToUniversalTime() : null, + DeviceCommDistributionStatus = items[8].Maxlength(200), + BrandName = items[9].Maxlength(80), + VersionModelNumber = items[10].Maxlength(80), + CatalogNumber = items[11].Maxlength(80), + DunsNumber = items[12].Maxlength(16), + CompanyName = items[13].Maxlength(120).ReplaceSpecialCharacters(), + DeviceCount = int.TryParse(items[14], out var result3) ? result3 : null, + DeviceDescription = items[15].Maxlength(2000).ReplaceSpecialCharacters(), + DmExempt = items[16].AsBoolean(), + PremarketExempt = items[17].AsBoolean(), + DeviceHctp = items[18].AsBoolean(), + DeviceKit = items[19].AsBoolean(), + DeviceCombinationProduct = items[20].AsBoolean(), + SingleUse = items[21].AsBoolean(), + LotBatch = items[22].AsBoolean(), + SerialNumber = items[23].AsBoolean(), + ManufacturingDate = items[24].AsBoolean(), + ExpirationDate = items[25].AsBoolean(), + DonationIdNumber = items[26].AsBoolean(), + LabeledContainsNrl = items[27].AsBoolean(), + LabeledNoNrl = items[28].AsBoolean(), + MriSafetyStatus = items[29].Maxlength(50), + Rx = items[30].AsBoolean(), + Otc = items[31].AsBoolean(), + DeviceSterile = items[32].AsBoolean(), + SterilizationPriorToUse = items[33].AsBoolean() + }; + } + catch (Exception ex) + { + return null; + } + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDIdentifier.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDIdentifier.cs new file mode 100644 index 0000000..0c9d57c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDIdentifier.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + [Index(nameof(DeviceId), nameof(PrimaryDI), nameof(DeviceIdType), IsUnique = true)] + [Index(nameof(DeviceId), nameof(PrimaryDI))] + [Index(nameof(DeviceId))] + [Index(nameof(PrimaryDI))] + public class GUDIDIdentifier + { + [Key] + public int GudidIdentifierId { get; set; } + public string PrimaryDI { get; set; } + public string DeviceId { get; set; } + public string DeviceIdType { get; set; } + public string DeviceIdIssuingAgency { get; set; } + public string ContainsDINumber { get; set; } + public int PkgQuantity { get; set; } + public DateTime? PkgDiscontinueDate { get; set; } + public string PkgStatus { get; set; } + public string PkgType { get; set; } + [DefaultValue(false)] + public bool IsDeleted { get; set; } = false; + } + + [Index(nameof(DeviceId), nameof(PrimaryDI), nameof(DeviceIdType), IsUnique = true)] + [Table("gudid_identifiers", Schema = "tmp")] + public class TmpGUDIDIdentifier + { + [Key] + public int TmpGudidIdentifierId { get; set; } + public string PrimaryDI { get; set; } + public string DeviceId { get; set; } + public string DeviceIdType { get; set; } + public string DeviceIdIssuingAgency { get; set; } + public string ContainsDINumber { get; set; } + public int PkgQuantity { get; set; } + public DateTime? PkgDiscontinueDate { get; set; } + public string PkgStatus { get; set; } + public string PkgType { get; set; } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDIdentifierExtensions.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDIdentifierExtensions.cs new file mode 100644 index 0000000..08c1833 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDIdentifierExtensions.cs @@ -0,0 +1,48 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public static class GUDIDIdentifierExtensions + { + public static GUDIDIdentifier GUDIDIdentifier(this string line) + { + var items = line.Split('|'); + if (items.Length == 9) + { + return new GUDIDIdentifier() + { + PrimaryDI = items[0], + DeviceId = items[1], + DeviceIdType = items[2], + DeviceIdIssuingAgency = items[3], + ContainsDINumber = items[4], + PkgQuantity = int.TryParse(items[5], out int result) ? result : 0, + PkgDiscontinueDate = DateTime.TryParse(items[6], out var date) ? date.ToUniversalTime() : null, + PkgStatus = items[7], + PkgType = items[8] + }; + } + return null; + } + public static TmpGUDIDIdentifier TmpGUDIDIdentifier(this string line) + { + var items = line.Split('|'); + if (items.Length == 9) + { + return new TmpGUDIDIdentifier() + { + PrimaryDI = items[0], + DeviceId = items[1], + DeviceIdType = items[2], + DeviceIdIssuingAgency = items[3], + ContainsDINumber = items[4], + PkgQuantity = int.TryParse(items[5], out int result) ? result : 0, + PkgDiscontinueDate = DateTime.TryParse(items[6], out var date) ? date.ToUniversalTime() : null, + PkgStatus = items[7], + PkgType = items[8] + }; + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDService.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDService.cs new file mode 100644 index 0000000..f104e37 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDService.cs @@ -0,0 +1,157 @@ +using EFCore.BulkExtensions; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public class GUDIDService : IGUDIDService + { + private readonly CentralDbContext _centralDbContext; + private readonly ILogger _logger; + private readonly IHubContext _notificationHubContext; + + public GUDIDService(CentralDbContext centralDbContext, + IHubContext notificationHubContext, + ILogger logger) + { + _centralDbContext = centralDbContext; + _notificationHubContext = notificationHubContext; + _logger = logger; + } + + public async Task SeedGUDID(string schemaName, string tableName, Stream stream, string fileName, string userName, CancellationToken cancellationToken) + { + var toastKey = userName + fileName; + var hub = _notificationHubContext.Clients.Group(toastKey); + + await hub.SendUploadFileStart(toastKey, fileName); + + try + { + switch (tableName) + { + case "gudid_identifiers": + await SeedTable(schemaName, tableName, stream, TmpGudidIdentifierFunc, cancellationToken); + break; + case "gudid_terms": + await SeedTable(schemaName, tableName, stream, TmpGudidTermFunc, cancellationToken); + break; + case "gudid_devices": + await SeedTable(schemaName, tableName, stream, TmpGudidDevicesFunc, cancellationToken); + break; + } + + await hub.SendUploadFileFinish(toastKey, fileName); + } + catch (Exception ex) + { + _logger.LogError(ex, ex.Message, ex.StackTrace, nameof(SeedGUDID)); + await hub.SendUploadFileError(toastKey, fileName); + } + } + + public async Task SeedTable(string schemaName, string tableName, Stream stream, + Func, bool> function, CancellationToken cancellationToken) where TEntity : class + { + string line; + var identifiersLookup = new HashSet<(string, string, string)>(); + // Truncate existing data + _centralDbContext.Database.SetCommandTimeout(TimeSpan.FromMinutes(10)); + _logger.LogServiceInformation("Begin table truncate"); + await _centralDbContext.TruncateAsync(cancellationToken: cancellationToken); + _logger.LogServiceInformation("End table truncate"); + await using var importer = await _centralDbContext.BeginTextImportAsync(cancellationToken: cancellationToken); + using var reader = new StreamReader(stream, bufferSize: 1024 * 64); + _logger.LogServiceInformation("Begin streaming file"); + await reader.ReadLineAsync(cancellationToken); // Skip header line + do + { + line = await reader.ReadLineAsync(cancellationToken); + if (string.IsNullOrEmpty(line) || line.Length == 0) { continue; } + var items = line.Split('|'); + if (!(function?.Invoke(items, identifiersLookup) ?? true)) { continue; } + line = string.Join("|", items); + importer.WriteLine(line); + } while (line != null); + _logger.LogServiceInformation("End streaming file"); + + } + + public static bool TmpGudidDevicesFunc(string[] items, HashSet<(string, string, string)> identifiersLookup) + { + if (items.Length != 34) { return false; } + + try + { + int? tmp = int.TryParse(items[4], out var result4) ? result4 : null; + items[4] = tmp?.ToString(); + DateTime? PublicVersionDate = DateTime.TryParse(items[5], out var date) ? date.ToUniversalTime() : null; + items[5] = PublicVersionDate?.ToString("o") ?? "\\N"; // format this date, use the postgres nullable placeholder + DateTime? DevicePublishDate = DateTime.TryParse(items[6], out var date2) ? date2.ToUniversalTime() : null; + items[6] = DevicePublishDate?.ToString("o") ?? "\\N"; // format this date, use the postgres nullable placeholder + DateTime? DeviceCommDistributionEndDate = DateTime.TryParse(items[7], out var date3) ? date3.ToUniversalTime() : null; + items[7] = DeviceCommDistributionEndDate?.ToString("o") ?? "\\N"; // format this date, use the postgres nullable placeholder + items[8] = items[8].Maxlength(200); + items[9] = items[9].ReplaceSpecialCharacters().Maxlength(80); + items[10] = items[10].Maxlength(80); + items[11] = items[11].Maxlength(80); + items[12] = items[12].Maxlength(16); + items[13] = items[13].ReplaceSpecialCharacters().Maxlength(120); + int? tmp2 = int.TryParse(items[14], out var result3) ? result3 : null; + items[14] = tmp2?.ToString(); + items[15] = items[15].ReplaceSpecialCharacters().Maxlength(2000); + items[29] = items[29].Maxlength(50); + + for (var i = 16; i < 34; i++) + { + if (i == 29) continue; + items[i] = items[i].TrueFalse(); + } + } + catch (Exception ex) + { + return false; + } + + return true; + } + + public static bool TmpGudidTermFunc(string[] items, HashSet<(string, string, string)> identifiersLookup) + { + if (items.Length != 6) { return false; } + items[1].ReplaceSpecialCharacters(); + items[4] = items[4] == "Active" ? "0" : "1"; + items[5] = items[5].ToLower() == "true" ? "true" : "false"; + + return true; + } + + public static bool TmpGudidIdentifierFunc(string[] items, HashSet<(string, string, string)> identifiersLookup) + { + if (items.Length != 9) { return false; } // 9 items + if (identifiersLookup.Contains((items[0], items[1], items[2]))) { return false; } // check for duplicates + identifiersLookup.Add((items[0], items[1], items[2])); + if (string.IsNullOrEmpty(items[5])) items[5] = "0"; // the default for this columns is 0 + if (items[6].Length > 0) + { + DateTime? PkgDiscontinueDate = DateTime.TryParse(items[6], out var date) ? date.ToUniversalTime() : null; + items[6] = PkgDiscontinueDate?.ToString("o") ?? "\\N"; // format this date, use the postgres nullable placeholder + } + items.ForEach((item, idx) => + { + if (string.IsNullOrEmpty(item)) { items[idx] = "\\N"; } // use the postgres nullable placeholder + }); + return true; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDTerm.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDTerm.cs new file mode 100644 index 0000000..3564fc4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/GUDIDTerm.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + [Index(nameof(PrimaryDI))] + public class GUDIDTerm + { + [Key] + public int GUDIDTermId { get; set; } + public string PrimaryDI { get; set; } + [MaxLength(150)] + public string GMDNPTName { get; set; } + [MaxLength(2000)] + public string GMDNPTDefinition { get; set; } + public string GMDNCode { get; set; } + public GMDNCodeStatus GMDNCodeStatus { get; set; } + public bool Implantable { get; set; } + } + + public enum GMDNCodeStatus + { + Active, + Obsolete + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/IGMDNService.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/IGMDNService.cs new file mode 100644 index 0000000..0170822 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/IGMDNService.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public interface IGMDNService + { + Task SeedGMDNCategories(IFormFile gmdnTermCategoryFile, string userName, CancellationToken cancellationToken); + Task SeedGMDNTermCategories(IFormFile gmdnTermCategoryFile, string userName, CancellationToken cancellationToken); + Task SeedGMDNTerms(IFormFile gmdnTermFile, string userName, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/IGUDIDService.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/IGUDIDService.cs new file mode 100644 index 0000000..35fff42 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/IGUDIDService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN +{ + public interface IGUDIDService + { + Task SeedGUDID(string schemaName, string tableName, Stream stream, string fileName, string userName, CancellationToken cancellationToken); + + Task SeedTable(string schemaName, string tableName, Stream stream, Func, bool> function, CancellationToken cancellationToken) where TEntity : class; + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierClearingJob.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierClearingJob.cs new file mode 100644 index 0000000..2db72a1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierClearingJob.cs @@ -0,0 +1,36 @@ +using Hangfire; +using Strata.ContinuousImprovement.Biz.ClientLicense; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + public class DeviceIdentifierClearingJob + { + private readonly IClientLicenseService _clientFilterService; + private readonly IBackgroundJobClient _backgroundJobClient; + + public DeviceIdentifierClearingJob( + IClientLicenseService clientFilterService, + IBackgroundJobClient backgroundJobClient) + { + _clientFilterService = clientFilterService; + _backgroundJobClient = backgroundJobClient; + } + + [DeleteOnSuccess] + [JobDisplayName("Clear Device Identifier Dimension in JAZZ for all non-licensed CI clients")] + public async Task ClearDimDeviceIdentifierFromJazz(CancellationToken cancellationToken) + { + // Get non-licensed clients (isLicensed: false) + var clearClients = await _clientFilterService.GetEligibleCIClientsAsync(isLicensed: false, cancellationToken); + + foreach (var client in clearClients) + { + _backgroundJobClient.Enqueue(job + => job.ClearClientDimDeviceIdentifier(client.DatabaseGUID, client.StrataId, cancellationToken)); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierClientClearingJob.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierClientClearingJob.cs new file mode 100644 index 0000000..c68a83b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierClientClearingJob.cs @@ -0,0 +1,79 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + class DeviceIdentifierClientClearingJob + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ILogger _logger; + + public DeviceIdentifierClientClearingJob(IAsyncDbContextFactory dbContextFactory, + ILogger logger) + { + _dbContextFactory = dbContextFactory; + _logger = logger; + } + + [DeleteOnSuccess] + [JobDisplayName("Clear Device Identifier Dimension in Jazz for client ({1})")] + [Semaphore(nameof(ClearClientDimDeviceIdentifier))] + [CaptureJobParameter(DatabaseGuidArgumentPosition = 0, StrataIdArgumentPosition = 1)] + public async Task ClearClientDimDeviceIdentifier(Guid databaseGuid, int strataId, CancellationToken cancellationToken) + { + await using var jazzDbContext = GetDbContext(cancellationToken).Result; + + try + { + // Get all device identifiers (exclude not-specified record) + var deviceIdentifiers = await jazzDbContext.DimDeviceIdentifiers + .Where(s => !string.IsNullOrEmpty(s.DeviceIdentifier)) // "" = Not Specified + .Select(s => s.DeviceIdentifier) + .ToListAsync(cancellationToken); + + var totalRowsAffected = 0; + + foreach (var batch in deviceIdentifiers.Batch(500)) + { + var identifiersToRemove = await jazzDbContext.DimDeviceIdentifiers + .Where(s => batch.Contains(s.DeviceIdentifier)) + .ToListAsync(cancellationToken); + + jazzDbContext.DimDeviceIdentifiers.RemoveRange(identifiersToRemove); + var rowsAffected = await jazzDbContext.SaveChangesAsync(cancellationToken); + totalRowsAffected += rowsAffected; + } + + if (totalRowsAffected > 0) + { + _logger.LogInformation("Cleared {TotalCount} device identifier records for client {StrataId}", totalRowsAffected, strataId); + } + else + { + _logger.LogInformation("No device identifier records to clear for client {StrataId}", strataId); + } + } + catch (Exception ex) + { + _logger.LogMappingJobError(ex, jazzDbContext, "Error clearing device identifier dimension"); + throw; + } + } + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierClientMappingJob.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierClientMappingJob.cs new file mode 100644 index 0000000..26e2b5a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierClientMappingJob.cs @@ -0,0 +1,151 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.Cube; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.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.ContinuousImprovement.Biz.GMDN.Jobs +{ + public class DeviceIdentifierClientMappingJob + { + private readonly CentralDbContext _centralDbContext; + protected readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly ILogger _logger; + private readonly ICubeService _cubeService; + + + public DeviceIdentifierClientMappingJob( + CentralDbContext centralDbContext, + IAsyncDbContextFactory dbContextFactory, + IBackgroundJobClient backgroundJobClient, + ILogger logger, + ICubeService cubeService) + { + _centralDbContext = centralDbContext; + _dbContextFactory = dbContextFactory; + _backgroundJobClient = backgroundJobClient; + _logger = logger; + _cubeService = cubeService; + } + + [DeleteOnSuccess] + [JobDisplayName("Map DimDeviceIdentifier to Client Jazz DB ({1})")] + [Semaphore(nameof(MapClientDimDeviceIdentifier))] + [CaptureJobParameter(DatabaseGuidArgumentPosition = 0, StrataIdArgumentPosition = 1)] + public async Task MapClientDimDeviceIdentifier(Guid databaseGuid, int strataId, CancellationToken cancellationToken) + { + try + { + if (await _cubeService.IsCubeRunningAsync(cancellationToken)) + { + await RetryMapClientDimDeviceIdentifierAsync(databaseGuid, strataId, 1, cancellationToken); + return; + } + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "CubeService Error mapping DimDeviceIdentifier to Jazz", + new Dictionary { + { "db", new Dictionary + { + { "databaseGuid", databaseGuid}, + { "strataId", strataId } + } + } + }); + } + + await using var jazzDbContext = GetDbContext(cancellationToken).Result; + try + { + + var supplies = await jazzDbContext.DimSupply + .Where(s => !string.IsNullOrEmpty(s.DeviceIdentifier)) + .Select(s => s.DeviceIdentifier).ToListAsync(cancellationToken); + + foreach (var batch in supplies.Batch(500)) + { + var centralRecords = await _centralDbContext.DeviceIdentifierDimension + .Where(x => batch.Contains(x.DeviceIdentifier)) + .ToListAsync(cancellationToken); + var jazzRecords = await jazzDbContext.DimDeviceIdentifiers + .Where(x => batch.Contains(x.DeviceIdentifier)) + .ToListAsync(cancellationToken); + var updates = centralRecords + .GroupJoin(jazzRecords, cr => cr.DeviceIdentifier, jr => jr.DeviceIdentifier, + (centralRecord, jazzRecord) => new + { + centralRecord, + jazzRecord = jazzRecord.DefaultIfEmpty().FirstOrDefault() + }) + .ToList(); + await jazzDbContext.DimDeviceIdentifiers.AddRangeAsync(updates.Where(update => update.jazzRecord == null) + .Select(update => update.centralRecord.MapToJazzModel()), cancellationToken); + foreach (var update in updates.Where(update => update.jazzRecord != null && update.jazzRecord.HasChanges(update.centralRecord))) + { + update.jazzRecord.Update(update.centralRecord); + jazzDbContext.DimDeviceIdentifiers.Update(update.jazzRecord); + } + + // save batch + await jazzDbContext.SaveChangesAsync(cancellationToken); + } + } + catch (Exception ex) + { + if (ex.Message.Contains("Invalid column name 'DeviceIdentifier'.")) + { + _logger.LogMappingJobInformation(ex, jazzDbContext, "Error mapping DimDeviceIdentifier to Jazz"); + } + else + { + _logger.LogMappingJobError(ex, jazzDbContext, "Error mapping DimDeviceIdentifier to Jazz"); + } + } + } + + [DeleteOnSuccess] + [JobDisplayName("Map DimDeviceIdentifier to Client Jazz DB ({1})")] + [CaptureJobParameter(DatabaseGuidArgumentPosition = 0, StrataIdArgumentPosition = 1)] + public async Task RetryMapClientDimDeviceIdentifierAsync(Guid databaseGuid, int strataId, int tries, CancellationToken cancellationToken) + { + if (tries > 5) { return; } + if (await _cubeService.IsCubeRunningAsync(cancellationToken)) + { + _logger.LogServiceInformation("Cannot map device identifier dimension to client db while cube is processing", + new Dictionary { + { "db", new Dictionary + { + { "databaseGuid", databaseGuid}, + { "strataId", strataId } + } + } + }); + + _backgroundJobClient.Schedule(job + => job.RetryMapClientDimDeviceIdentifierAsync(databaseGuid, strataId, tries + 1, cancellationToken), TimeSpan.FromMinutes(30)); + } + else + { + _backgroundJobClient.Enqueue(job + => job.MapClientDimDeviceIdentifier(databaseGuid, strataId, cancellationToken)); + } + } + + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierMappingJob.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierMappingJob.cs new file mode 100644 index 0000000..3e29f2f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceIdentifierMappingJob.cs @@ -0,0 +1,70 @@ +using Hangfire; +using Strata.ContinuousImprovement.Biz.ClientLicense; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.Id.Client; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + class DeviceIdentifierMappingJob + { + private readonly IClientLicenseService _clientLicenseService; + private readonly IIdServiceClient _idServiceClient; + private readonly IBackgroundJobClient _backgroundJobClient; + + public DeviceIdentifierMappingJob( + IClientLicenseService clientLicenseService, + IIdServiceClient idServiceClient, + IBackgroundJobClient backgroundJobClient) + { + _clientLicenseService = clientLicenseService; + _idServiceClient = idServiceClient; + _backgroundJobClient = backgroundJobClient; + } + + [DeleteOnSuccess] + [JobDisplayName("Update Device Identifier Dimension in JAZZ for all clients")] + public async Task MapDimDeviceIdentifierToJazz(CancellationToken cancellationToken) + { + // Get licensed clients (isLicensed: true) + var refreshClients = await _clientLicenseService.GetEligibleCIClientsAsync(isLicensed: true, cancellationToken); + + foreach (var client in refreshClients) + { + if (client.IsInMaintenanceMode) + { + _backgroundJobClient.Schedule(job + => job.MapDimDeviceIdentifierToJazzInMaintenanceMode(client.StrataId, 1, cancellationToken), + TimeSpan.FromMinutes(30)); + } + else + { + _backgroundJobClient.Enqueue(job + => job.MapClientDimDeviceIdentifier(client.DatabaseGUID, client.StrataId, cancellationToken)); + } + } + } + + [DeleteOnSuccess] + [JobDisplayName("Update Device Identifier Dimension in JAZZ for client in maintenance mode ({0})")] + public async Task MapDimDeviceIdentifierToJazzInMaintenanceMode(int strataId, int tries, CancellationToken cancellationToken) + { + if (tries > 5) { return; } + var client = await _idServiceClient.GetStrataId(strataId, cancellationToken); + if (client == null) { return; } + if (client.IsInMaintenanceMode) + { + _backgroundJobClient.Schedule(job + => job.MapDimDeviceIdentifierToJazzInMaintenanceMode(client.StrataId, tries + 1, cancellationToken), TimeSpan.FromMinutes(30)); + } + else + { + if (client == null) { return; } + _backgroundJobClient.Enqueue(job + => job.MapClientDimDeviceIdentifier(client.DatabaseGUID, client.StrataId, cancellationToken)); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceTermCategory.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceTermCategory.cs new file mode 100644 index 0000000..ec2494e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/DeviceTermCategory.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + public class DeviceTermCategory + { + public string CategoryName { get; set; } + public string TermCode { get; set; } + public string TermName { get; set; } + public string TermDefinition { get; set; } + public string Path { get; set; } + public int NodeId { get; set; } + } + + public static class DeviceTermCategoryExtensions + { + public static string GetHierarchyNameAtIndex(this List deviceTermCategories, string[] hierarchies, int index) + { + return deviceTermCategories.Where(x => x.NodeId.ToString() == hierarchies.ElementAtOrDefault(index)) + .Select(x => x.CategoryName).FirstOrDefault() ?? ""; + } + + public static string[] GetHierarchiesFromPath(this List deviceTermCategories, string hierarchyPath) + { + // Get the longest path for this hierarchy with the greatest node value + var fullPath = deviceTermCategories.Where(x => x.Path.StartsWith(hierarchyPath)) + .OrderByDescending(x => x.Path.Length).ThenByDescending(x => x.NodeId) + .Select(x => x.Path).FirstOrDefault(); + + // skip first and last elements (path is wrapped in "/" causing empty elements during split) + return fullPath?[1..^1].Split("/"); + } + } +} + + + diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/GMDNFTPSection.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/GMDNFTPSection.cs new file mode 100644 index 0000000..7113dcc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/GMDNFTPSection.cs @@ -0,0 +1,15 @@ +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + public class GMDNFTPSection + { + public string FTPHostUrl { get; set; } + public UserInfo TermUser { get; set; } + public UserInfo CategoryUser { get; set; } + } + + public class UserInfo + { + public string UserName { get; set; } + public string Password { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/GMDNMonthlyUpdateJob.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/GMDNMonthlyUpdateJob.cs new file mode 100644 index 0000000..e8dbb00 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/GMDNMonthlyUpdateJob.cs @@ -0,0 +1,92 @@ +using FluentFTP; +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using static Strata.ContinuousImprovement.Biz.GMDN.GMDNService; + +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + public class GMDNMonthlyUpdateJob + { + private readonly CentralDbContext _centralDbContext; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly ILogger _logger; + private readonly GMDNFTPSection _gmdnFTPInfo; + + public GMDNMonthlyUpdateJob( + CentralDbContext centralDbContext, + IBackgroundJobClient backgroundJobClient, + IOptions sectionAccessor, + ILogger logger) + { + _centralDbContext = centralDbContext; + _backgroundJobClient = backgroundJobClient; + _gmdnFTPInfo = sectionAccessor.Value ?? throw new ArgumentNullException(nameof(sectionAccessor)); + _logger = logger; + } + + [DeleteOnSuccess] + [JobDisplayName("Refresh GMDN Data and Reseed Device Identifier Dimension")] + [Semaphore(nameof(GMDNMonthlyUpdateJob.RefreshGMDNData))] + public async Task RefreshGMDNData(CancellationToken cancellationToken) + { + try + { + // Read GMDN files from FTP and re-populate tables + await RefreshGMDNTerms(); + await RefreshGMDNCategories(); + + // Reseed PrimaryDI Dimension w/ updated data + _backgroundJobClient.Enqueue(job + => job.SeedDeviceIdentifierDimension(cancellationToken)); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + } + } + + private async Task RefreshGMDNTerms() + { + var termsClient = new FtpClient(_gmdnFTPInfo.FTPHostUrl, _gmdnFTPInfo.TermUser.UserName, _gmdnFTPInfo.TermUser.Password, 21, new FtpConfig() { EncryptionMode = FtpEncryptionMode.Explicit}); + var termFile = FTPClientExtensions.DeserializeZippedXMLFromFTP(termsClient, "gmdnData", "gmdn"); + + if (termFile.terms.Any()) + { + await _centralDbContext.Database.ExecuteSqlRawAsync("TRUNCATE TABLE public.gmdn_terms RESTART IDENTITY;"); + await _centralDbContext.GMDNTerms.AddRangeAsync(termFile.terms); + await _centralDbContext.SaveChangesAsync(); + } + } + + private async Task RefreshGMDNCategories() + { + var categoryClient = new FtpClient(_gmdnFTPInfo.FTPHostUrl, _gmdnFTPInfo.CategoryUser.UserName, _gmdnFTPInfo.CategoryUser.Password, 21, new FtpConfig() { EncryptionMode = FtpEncryptionMode.Explicit }); + var categoryFile = FTPClientExtensions.DeserializeZippedXMLFromFTP(categoryClient, "gmdn_category_en_", "gmdn"); + + if (categoryFile.categories.Any()) + { + await _centralDbContext.Database.ExecuteSqlRawAsync("TRUNCATE TABLE public.gmdn_categories RESTART IDENTITY;"); + await _centralDbContext.GMDNCategories.AddRangeAsync(categoryFile.categories); + await _centralDbContext.SaveChangesAsync(); + } + + var termCategoryFile = FTPClientExtensions.DeserializeZippedXMLFromFTP(categoryClient, "gmdn_categoryTermLink_en_", "gmdn"); + if (termCategoryFile.termCategories.Any()) + { + await _centralDbContext.Database.ExecuteSqlRawAsync("TRUNCATE TABLE public.gmdn_term_categories RESTART IDENTITY;"); + await _centralDbContext.GMDNTermCategories.AddRangeAsync(termCategoryFile.termCategories); + await _centralDbContext.SaveChangesAsync(); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/GUDIDDeviceDailyUpdateJob.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/GUDIDDeviceDailyUpdateJob.cs new file mode 100644 index 0000000..4afb082 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/GUDIDDeviceDailyUpdateJob.cs @@ -0,0 +1,164 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using RestSharp; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + public class GUDIDDeviceDailyUpdateJob : IGUDIDDeviceDailyUpdateJob + { + private readonly CentralDbContext _centralDbContext; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly ILogger _logger; + private readonly RestClient _restClient = new RestClient(new RestClientOptions(@"https://accessgudid.nlm.nih.gov/api/v2")); + + public GUDIDDeviceDailyUpdateJob( + CentralDbContext centralDbContext, + IBackgroundJobClient backgroundJobClient, + ILogger logger) + { + _centralDbContext = centralDbContext; + _backgroundJobClient = backgroundJobClient; + _logger = logger; + } + + [DeleteOnSuccess] + [JobDisplayName("Refresh GUDID Devices")] + [Semaphore(nameof(GUDIDDeviceDailyUpdateJob.GUDIDDeviceDailyUpdate))] + public async Task GUDIDDeviceDailyUpdate(CancellationToken cancellationToken) + { + var maxPublicVersionDate = (await _centralDbContext.GUDIDDevices.MaxAsync(d => d.PublicVersionDate)) ?? DateTime.MinValue; + var fromDate = maxPublicVersionDate.ToString("yyyyMMdd"); + var request = new RestRequest("devices/implantable/list.json"); + request.AddOrUpdateParameter("from_date", fromDate); + request.AddOrUpdateParameter("per_page", "100"); + request.AddOrUpdateParameter("page", "1"); + var response = await _restClient.GetAsync(request); + for (var pageNo = 1; pageNo < response.Metadata.TotalPages; pageNo++) + { + _backgroundJobClient.Enqueue((job) => + job.GUDIDDeviceDailyPageUpdate(fromDate, pageNo, cancellationToken)); + } + } + + [DeleteOnSuccess] + [JobDisplayName("Refresh GUDID Devices for page {1}")] + [Semaphore(nameof(GUDIDDeviceDailyUpdateJob.GUDIDDeviceDailyPageUpdate))] + public async Task GUDIDDeviceDailyPageUpdate(string fromDate, int pageNo, CancellationToken cancellationToken) + { + var request = new RestRequest("devices/implantable/list.json"); + request.AddOrUpdateParameter("from_date", fromDate); + request.AddOrUpdateParameter("per_page", "100"); + request.AddOrUpdateParameter("page", $"{pageNo}"); + var response = await _restClient.GetAsync(request); + var newDevices = response.Devices + .Where(d => !_centralDbContext.GUDIDDevices.Any(g => g.PrimaryDi == d.DeviceId)) + .Select(d => GUDIDDevice(d)); + var updatedDevices = response.Devices + .Where(d => _centralDbContext.GUDIDDevices.Any(g => g.PrimaryDi == d.DeviceId)) + .Select(d => GUDIDDevice(d)); + try + { + if (newDevices.Any()) + { + await _centralDbContext.GUDIDDevices.AddRangeAsync(newDevices); + } + if (updatedDevices.Any()) + { + _centralDbContext.GUDIDDevices.UpdateRange(updatedDevices); + } + var dict = new Dictionary + { + {"Page", pageNo }, + {"Total Pages", response.Metadata.TotalPages }, + {"New Devices", newDevices.Count() }, + {"Updated Devices", updatedDevices.Count() } + }; + _logger.LogServiceInformation(nameof(GUDIDDeviceDailyUpdate).SplitCamelCase(), dict); + await _centralDbContext.SaveChangesAsync(cancellationToken); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + throw; + } + } + + private DateTime MaxDateTime(DateTime date1, DateTime date2) + => new DateTime(Math.Max(date1.Ticks, date2.Ticks)); + + public GUDIDDevice GUDIDDevice(Device device) + { + var guididDevice = _centralDbContext.GUDIDDevices.FirstOrDefault(d => d.PrimaryDi == device.DeviceId); + return guididDevice == null ? new GUDIDDevice + { + PrimaryDi = device.DeviceId, + PublicDeviceRecordKey = device.PublicDeviceRecordKey, + PublicVersionNumber = device.PublicVersionNumber, + PublicVersionDate = device.PublicVersionDate, + BrandName = device.BrandName, + CompanyName = device.CompanyName, + VersionModelNumber = device.VersionModelNumber, + DunsNumber = device.DunsNumber, + MriSafetyStatus = device.MriSafetyStatus, + LabeledContainsNrl = device.LabeledContainsNRL, + DeviceRecordStatus = device.DeviceRecordStatus + } : GUDIDDevice(device, guididDevice); + } + + public GUDIDDevice GUDIDDevice(Device device, GUDIDDevice gudidDevice) + { + gudidDevice.PublicDeviceRecordKey = device.PublicDeviceRecordKey; + gudidDevice.PublicVersionNumber = device.PublicVersionNumber; + gudidDevice.PublicVersionDate = device.PublicVersionDate; + gudidDevice.BrandName = device.BrandName; + gudidDevice.CompanyName = device.CompanyName; + gudidDevice.VersionModelNumber = device.VersionModelNumber; + gudidDevice.DunsNumber = device.DunsNumber; + gudidDevice.MriSafetyStatus = device.MriSafetyStatus; + gudidDevice.LabeledContainsNrl = device.LabeledContainsNRL; + gudidDevice.DeviceRecordStatus = device.DeviceRecordStatus; + return gudidDevice; + } + + public class Device + { + public string DeviceId { get; set; } + public string DeviceIdIssuingAgency { get; set; } + public string PublicDeviceRecordKey { get; set; } + public int PublicVersionNumber { get; set; } + public DateTime PublicVersionDate { get; set; } + public string BrandName { get; set; } + public string CompanyName { get; set; } + public string VersionModelNumber { get; set; } + public string DunsNumber { get; set; } + public string MriSafetyStatus { get; set; } + public bool LabeledContainsNRL { get; set; } + public List GmdnPTName { get; set; } + public string DeviceRecordStatus { get; set; } + public DateTime CreationDate { get; set; } + } + + public class Metadata + { + public int Page { get; set; } + public int TotalPages { get; set; } + public int TotalResults { get; set; } + } + + public class GUDID + { + public Metadata Metadata { get; set; } + public List Devices { get; set; } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/IGUDIDDeviceDailyUpdateJob.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/IGUDIDDeviceDailyUpdateJob.cs new file mode 100644 index 0000000..d054b63 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/IGUDIDDeviceDailyUpdateJob.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + public interface IGUDIDDeviceDailyUpdateJob + { + Task GUDIDDeviceDailyUpdate(CancellationToken cancellationToken); + Task GUDIDDeviceDailyPageUpdate(string fromDate, int pageNo, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/SeedDeviceIdentifierDimensionJob.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/SeedDeviceIdentifierDimensionJob.cs new file mode 100644 index 0000000..9248cf6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/SeedDeviceIdentifierDimensionJob.cs @@ -0,0 +1,205 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + public partial class SeedDeviceIdentifierDimensionJob + { + private readonly CentralDbContext _centralDbContext; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly ILogger _logger; + private const string FeaturedAttributePath = "/1/"; + private const string NamesPath = "/3/"; + private const string AnatomicalSpecialtyPath = "/4/"; + private const string MaterialsPath = "/887/"; + private const string PowerPath = "/888/"; + private const string UserFrequencyPath = "/890/"; + private const string InvasivenessPath = "/898/"; + private const string DeviceFunctionPath = "/4941/"; + + public SeedDeviceIdentifierDimensionJob(CentralDbContext centralDbContext, + IBackgroundJobClient backgroundJobClient, + ILogger logger) + { + _centralDbContext = centralDbContext; + _backgroundJobClient = backgroundJobClient; + _logger = logger; + } + + [DeleteOnSuccess] + [JobDisplayName("Seed DeviceIdentifierDimension")] + [Semaphore(nameof(SeedDeviceIdentifierDimension))] + public async Task SeedDeviceIdentifierDimension(CancellationToken cancellationToken) + { + _logger.LogServiceInformation($"{nameof(SeedDeviceIdentifierDimension)} Job Started."); + var startTime = DateTime.Now; + _centralDbContext.Database.SetCommandTimeout(TimeSpan.FromMinutes(10)); + _centralDbContext.Database.ExecuteSqlRaw("TRUNCATE TABLE public.device_identifier_dimension;"); + + var totalBatches = 10; + var batchSize = (int)Math.Ceiling(_centralDbContext.DeviceIdentifierTerms.Count().DivideBy(totalBatches)); + var currentBatch = 1; + + while (currentBatch <= totalBatches) + { + _backgroundJobClient.Enqueue(job => + job.SeedDeviceIdentifierDimensionBatch(totalBatches, currentBatch, batchSize)); + currentBatch++; + } + + _logger.LogServiceInformation($"{nameof(SeedDeviceIdentifierDimension)} Job Finished, elapsed time: {TimeSpan.FromSeconds(DateTime.Now.Subtract(startTime).Minutes)}"); + } + + [DeleteOnSuccess] + [JobDisplayName("Seed DeviceIdentifierDimension Batch ({1} of {0})")] + [Semaphore(nameof(SeedDeviceIdentifierDimensionBatch))] + public async Task SeedDeviceIdentifierDimensionBatch(int totalBatches, int batch, int batchSize) + { + try + { + _logger.LogServiceInformation($"{nameof(SeedDeviceIdentifierDimensionBatch)} Batch {batch} of {totalBatches} Job Started."); + var startTime = DateTime.Now; + _centralDbContext.Database.SetCommandTimeout(TimeSpan.FromMinutes(10)); + + var deviceTerms = (from gudidDeviceTerms in _centralDbContext.DeviceIdentifierTerms.AsNoTracking() + .Where(device => device.RowNumber == 1) + .OrderBy(x => x.DeviceId).Skip((batch - 1) * batchSize).Take(batchSize) + select new { gudidDeviceTerms.DeviceId, gudidDeviceTerms.DeviceIdType, gudidDeviceTerms.PrimaryDI, gudidDeviceTerms.CompanyName, gudidDeviceTerms.GMDNCode }).ToList(); + + var termCategories = (from gmdnTerms in _centralDbContext.GMDNTerms.AsNoTracking() + join gmdnTermCategories in _centralDbContext.GMDNTermCategories.AsNoTracking() on gmdnTerms.TermCode equals gmdnTermCategories.TermCode + join gmdnCategories in _centralDbContext.GMDNCategories.AsNoTracking() on gmdnTermCategories.CategoryCode equals gmdnCategories.CategoryCode + select new { gmdnTerms.TermCode, gmdnTerms.TermName, gmdnTerms.TermDefinition, gmdnCategories.Name, gmdnCategories.Path, gmdnCategories.NodeId }).ToList(); + + var totalUpdates = 0; + foreach (var deviceBatch in deviceTerms.Batch(500)) + { + var deviceIdentifierBatch = new List(); + foreach (var deviceTerm in deviceBatch) + { + var deviceTermCategories = termCategories.Where(termCategory => termCategory.TermCode == deviceTerm.GMDNCode).Select(dtc => new DeviceTermCategory + { + TermCode = dtc.TermCode, + TermName = dtc.TermName.ReplaceSpecialCharacters(), + TermDefinition = dtc.TermDefinition.ReplaceSpecialCharacters(), + Path = dtc.Path, + CategoryName = dtc.Name, + NodeId = dtc.NodeId + }).ToList(); + + if (deviceTermCategories.Any()) + { + var deviceIdentifier = new DeviceIdentifierDimension() + { + DeviceIdentifier = deviceTerm.DeviceId, + DeviceIdType = deviceTerm.DeviceIdType, + PrimaryDI = deviceTerm.PrimaryDI, + GMDNTermCode = deviceTerm.GMDNCode, + GUDIDCompanyName = deviceTerm.CompanyName, + GMDNTermName = deviceTermCategories?.FirstOrDefault().TermName, + GMDNTermDefinition = deviceTermCategories?.FirstOrDefault().TermDefinition + }; + var deviceHierarchies = deviceTermCategories.GetHierarchiesFromPath(DeviceFunctionPath); + if (deviceHierarchies?.Length > 0) + { + // Hierarchy Index starts at 1 in order to skip 0th element which is the parent category + // i.e. /4941/9057/10774/, here 4941 is device function parent category we just want it's sub categories + deviceIdentifier.GMDNDeviceFunction1 = deviceTermCategories.GetHierarchyNameAtIndex(deviceHierarchies, 1); + deviceIdentifier.GMDNDeviceFunction2 = deviceTermCategories.GetHierarchyNameAtIndex(deviceHierarchies, 2); + deviceIdentifier.GMDNDeviceFunction3 = deviceTermCategories.GetHierarchyNameAtIndex(deviceHierarchies, 3); + deviceIdentifier.GMDNDeviceFunction4 = deviceTermCategories.GetHierarchyNameAtIndex(deviceHierarchies, 4); + deviceIdentifier.GMDNDeviceFunction5 = deviceTermCategories.GetHierarchyNameAtIndex(deviceHierarchies, 5); + deviceIdentifier.GMDNDeviceFunction6 = deviceTermCategories.GetHierarchyNameAtIndex(deviceHierarchies, 6); + deviceIdentifier.GMDNDeviceFunction7 = deviceTermCategories.GetHierarchyNameAtIndex(deviceHierarchies, 7); + } + + var invasivenessHierarchies = deviceTermCategories.GetHierarchiesFromPath(InvasivenessPath); + if (invasivenessHierarchies?.Length > 0) + { + deviceIdentifier.GMDNInvasiveness1 = deviceTermCategories.GetHierarchyNameAtIndex(invasivenessHierarchies, 1); + deviceIdentifier.GMDNInvasiveness2 = deviceTermCategories.GetHierarchyNameAtIndex(invasivenessHierarchies, 2); + } + + var userFrequencyHierarchies = deviceTermCategories.GetHierarchiesFromPath(UserFrequencyPath); + if (userFrequencyHierarchies?.Length > 0) + { + deviceIdentifier.GMDNUseFrequency1 = deviceTermCategories.GetHierarchyNameAtIndex(userFrequencyHierarchies, 1); + deviceIdentifier.GMDNUseFrequency2 = deviceTermCategories.GetHierarchyNameAtIndex(userFrequencyHierarchies, 2); + } + + var powerPathHierarchies = deviceTermCategories.GetHierarchiesFromPath(PowerPath); + if (powerPathHierarchies?.Length > 0) + { + deviceIdentifier.GMDNPower1 = deviceTermCategories.GetHierarchyNameAtIndex(powerPathHierarchies, 1); + deviceIdentifier.GMDNPower2 = deviceTermCategories.GetHierarchyNameAtIndex(powerPathHierarchies, 2); + } + + var materialsPathHierarchies = deviceTermCategories.GetHierarchiesFromPath(MaterialsPath); + if (materialsPathHierarchies?.Length > 0) + { + deviceIdentifier.GMDNMaterials1 = deviceTermCategories.GetHierarchyNameAtIndex(materialsPathHierarchies, 1); + deviceIdentifier.GMDNMaterials2 = deviceTermCategories.GetHierarchyNameAtIndex(materialsPathHierarchies, 2); + } + + var featuredAttributesPathHierarchies = deviceTermCategories.GetHierarchiesFromPath(FeaturedAttributePath); + if (featuredAttributesPathHierarchies?.Length > 0) + { + deviceIdentifier.GMDNFeaturedAttributes1 = deviceTermCategories.GetHierarchyNameAtIndex(featuredAttributesPathHierarchies, 1); + deviceIdentifier.GMDNFeaturedAttributes2 = deviceTermCategories.GetHierarchyNameAtIndex(featuredAttributesPathHierarchies, 2); + } + + var anatomicalSpecialtyPathHierarchies = deviceTermCategories.GetHierarchiesFromPath(AnatomicalSpecialtyPath); + if (anatomicalSpecialtyPathHierarchies?.Length > 0) + { + deviceIdentifier.GMDNAnatomicalSpecialty1 = deviceTermCategories.GetHierarchyNameAtIndex(anatomicalSpecialtyPathHierarchies, 1); + deviceIdentifier.GMDNAnatomicalSpecialty2 = deviceTermCategories.GetHierarchyNameAtIndex(anatomicalSpecialtyPathHierarchies, 2); + deviceIdentifier.GMDNAnatomicalSpecialty3 = deviceTermCategories.GetHierarchyNameAtIndex(anatomicalSpecialtyPathHierarchies, 3); + deviceIdentifier.GMDNAnatomicalSpecialty4 = deviceTermCategories.GetHierarchyNameAtIndex(anatomicalSpecialtyPathHierarchies, 4); + deviceIdentifier.GMDNAnatomicalSpecialty5 = deviceTermCategories.GetHierarchyNameAtIndex(anatomicalSpecialtyPathHierarchies, 5); + deviceIdentifier.GMDNAnatomicalSpecialty6 = deviceTermCategories.GetHierarchyNameAtIndex(anatomicalSpecialtyPathHierarchies, 6); + } + + var namesIndexPathHierarchies = deviceTermCategories.GetHierarchiesFromPath(NamesPath); + if (namesIndexPathHierarchies?.Length > 0) + { + deviceIdentifier.GMDNNamesIndex1 = deviceTermCategories.GetHierarchyNameAtIndex(namesIndexPathHierarchies, 1); + deviceIdentifier.GMDNNamesIndex2 = deviceTermCategories.GetHierarchyNameAtIndex(namesIndexPathHierarchies, 2); + deviceIdentifier.GMDNNamesIndex3 = deviceTermCategories.GetHierarchyNameAtIndex(namesIndexPathHierarchies, 3); + deviceIdentifier.GMDNNamesIndex4 = deviceTermCategories.GetHierarchyNameAtIndex(namesIndexPathHierarchies, 4); + deviceIdentifier.GMDNNamesIndex5 = deviceTermCategories.GetHierarchyNameAtIndex(namesIndexPathHierarchies, 5); + deviceIdentifier.GMDNNamesIndex6 = deviceTermCategories.GetHierarchyNameAtIndex(namesIndexPathHierarchies, 6); + deviceIdentifier.GMDNNamesIndex7 = deviceTermCategories.GetHierarchyNameAtIndex(namesIndexPathHierarchies, 7); + } + + deviceIdentifierBatch.Add(deviceIdentifier); + } + } + + _centralDbContext.DeviceIdentifierDimension.AddRange(deviceIdentifierBatch); + totalUpdates += await _centralDbContext.SaveChangesAsync(); + } + + var jobTime = (DateTime.Now - startTime).TotalSeconds; + _logger.LogServiceInformation($"{nameof(SeedDeviceIdentifierDimensionBatch)} Batch {batch} of {totalBatches} Job Added {totalUpdates} in {jobTime} seconds."); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + } + } + } +} + + + diff --git a/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/SeedGUDIDIdentifiersDataJob.cs b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/SeedGUDIDIdentifiersDataJob.cs new file mode 100644 index 0000000..c21c93c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GMDN/Jobs/SeedGUDIDIdentifiersDataJob.cs @@ -0,0 +1,31 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GMDN.Jobs +{ + public class SeedGUDIDIdentifierDataJob + { + private readonly CentralDbContext _centralDbContext; + + public SeedGUDIDIdentifierDataJob( + CentralDbContext centralDbContext) + { + _centralDbContext = centralDbContext; + } + + [DeleteOnSuccess] + [JobDisplayName("Merge GUDIDIdentifierData in CentralDBContext")] + [Semaphore(nameof(MergeGUDIDIdentifierData))] + public async Task MergeGUDIDIdentifierData(CancellationToken cancellationToken) + { + _centralDbContext.Database.SetCommandTimeout(TimeSpan.FromMinutes(30)); + await _centralDbContext.Database.ExecuteSqlRawAsync("call public.update_gudid_identifiers();", cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/AccountDetail.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/AccountDetail.cs new file mode 100644 index 0000000..885b766 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/AccountDetail.cs @@ -0,0 +1,20 @@ +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + public class AccountDetail + { + public int AccountId { get; set; } + public string Name { get; set; } + public decimal Amount { get; set; } + + public AccountDetail() + { + } + + public AccountDetail(int accountId, string name, decimal amount) + { + AccountId = accountId; + Name = name; + Amount = amount; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/DepartmentDetail.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/DepartmentDetail.cs new file mode 100644 index 0000000..30f09e1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/DepartmentDetail.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + public class DepartmentDetail + { + public int DepartmentId { get; set; } + public string Name { get; set; } + public decimal Amount { get; set; } + public IEnumerable AccountDetails { get; set; } + + public DepartmentDetail() + { + } + + public DepartmentDetail(int departmentId, string name, decimal amount, IEnumerable accountDetails) + { + DepartmentId = departmentId; + Name = name; + Amount = amount; + AccountDetails = accountDetails; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactGL.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactGL.cs new file mode 100644 index 0000000..ba8031f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactGL.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + [Table("viewFactGL", Schema = "cci")] + public class FactGL : IGLData + { + public int DepartmentID { get; set; } + public int AccountID { get; set; } + public short FiscalYearID { get; set; } + public byte FiscalMonthID { get; set; } + [Precision(38, 4)] + public decimal Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactGLSampled.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactGLSampled.cs new file mode 100644 index 0000000..59fcce6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactGLSampled.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + [Table("viewFactGLSampled", Schema = "cci")] + public class FactGLSampled : IGLData + { + public int DepartmentID { get; set; } + public int AccountID { get; set; } + public short FiscalYearID { get; set; } + public byte FiscalMonthID { get; set; } + [Precision(38, 6)] + public decimal Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactStatistic.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactStatistic.cs new file mode 100644 index 0000000..153d669 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactStatistic.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + [Table("viewFactStatistic", Schema = "cci")] + public class FactStatistic : IGLData + { + public int DepartmentID { get; set; } + public int AccountID { get; set; } + public short FiscalYearID { get; set; } + public byte FiscalMonthID { get; set; } + [Precision(38, 4)] + public decimal Value { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactStatisticSampledMonthly.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactStatisticSampledMonthly.cs new file mode 100644 index 0000000..165ab91 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/FactStatisticSampledMonthly.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + [Table("viewFactStatisticSampledMonthly", Schema = "cci")] + public class FactStatisticSampledMonthly : IGLData + { + public int DepartmentID { get; set; } + public int AccountID { get; set; } + public short FiscalYearID { get; set; } + public byte FiscalMonthID { get; set; } + [Precision(38, 6)] + public decimal Value { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/GLDepartmentAccountData.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/GLDepartmentAccountData.cs new file mode 100644 index 0000000..a8b6e61 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/GLDepartmentAccountData.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + public class GLDepartmentAccountData + { + public IEnumerable Departments { get; set; } + public IEnumerable Accounts { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/GLDetail.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/GLDetail.cs new file mode 100644 index 0000000..9482bc3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/GLDetail.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + public class GLDetail + { + public decimal Total { get; set; } + public IEnumerable DepartmentDetails { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/GLDetailService.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/GLDetailService.cs new file mode 100644 index 0000000..8951641 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/GLDetailService.cs @@ -0,0 +1,132 @@ +using MoreLinq; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.CoreLib.Claims; +using Strata.Schema.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + public class GLDetailService : JazzDbService, IGLDetailService + { + private readonly ISchemaServiceClient _schemaServiceClient; + + public GLDetailService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, IClaimsPrincipalAccessor claimsPrincipalAccessor, ISchemaServiceClient schemaServiceClient) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor) + { + _schemaServiceClient = schemaServiceClient; + } + + public async Task GetOverallDetailAsync(Guid configurationGuid, GLDepartmentAccountData departmentAccountData, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return null; + + var configuration = dbContext.Set().FirstOrDefault(c => c.ConfigurationGuid == configurationGuid); + if (configuration == null) return null; + + if (!departmentAccountData.Departments.Any() || !departmentAccountData.Accounts.Any()) return new GLDetail(); + var departments = await GetDimensionMemberDictionary("Department", departmentAccountData.Departments, cancellationToken); + var accounts = await GetDimensionMemberDictionary("Account", departmentAccountData.Accounts, cancellationToken); + + var fiscalMonthDimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Fiscal Month"); + var fiscalMonths = await _schemaServiceClient.GetDimensionMembersByDimensionGuidAsync(fiscalMonthDimension.DimensionGuid); + var yearMonthDictionary = fiscalMonths.GetFiscalMonthDictionary(configuration.OrgDefinedEndDate); + + var factGLSampledData = dbContext.Set(); + var factGLData = dbContext.Set(); + IQueryable glData = factGLSampledData.Any() ? factGLSampledData : factGLData; + + var batchedDepartments = departments.Batch(1000); + var groupedGlData = new List>(); + foreach (var depts in batchedDepartments) + { + var deptsDictionary = depts.ToDictionary(); + var glGrouping = glData + .Where(gl => deptsDictionary.Keys.Contains(gl.DepartmentID)) + .Where(gl => accounts.Keys.Contains(gl.AccountID)) + .AsEnumerable() + .Where(gl => yearMonthDictionary[gl.FiscalMonthID] == gl.FiscalYearID) + .GroupBy(gl => gl.DepartmentID) + .ToList(); + groupedGlData.AddRange(glGrouping); + } + + var departmentDetails = new List(); + + groupedGlData.ForEach(data => + { + var accountDetails = data.Where(d => d.Value != 0).GroupBy(gl => gl.AccountID).Select(d => new AccountDetail(d.Key, accounts[d.Key], d.Sum(a => a.Value))); + var departmentDetail = new DepartmentDetail(data.Key, departments[data.Key], accountDetails.Sum(d => d.Amount), accountDetails); + departmentDetails.Add(departmentDetail); + }); + + return new GLDetail() { Total = departmentDetails.Sum(d => d.Amount), DepartmentDetails = departmentDetails }; + } + + public async Task GetStatisticDetailAsync(Guid configurationGuid, GLDepartmentAccountData departmentAccountData, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return null; + + var configuration = dbContext.Set().FirstOrDefault(c => c.ConfigurationGuid == configurationGuid); + if (configuration == null) return null; + + if (!departmentAccountData.Departments.Any() || !departmentAccountData.Accounts.Any()) return new GLDetail(); + var departments = await GetDimensionMemberDictionary("Department", departmentAccountData.Departments, cancellationToken); + var accounts = await GetDimensionMemberDictionary("Account", departmentAccountData.Accounts, cancellationToken); + + var fiscalMonthDimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Fiscal Month"); + var fiscalMonths = await _schemaServiceClient.GetDimensionMembersByDimensionGuidAsync(fiscalMonthDimension.DimensionGuid); + var yearMonthDictionary = fiscalMonths.GetFiscalMonthDictionary(configuration.OrgDefinedEndDate); + + var factStatisticSampledMonthlyData = dbContext.Set(); + var factStatisticData = dbContext.Set(); + IQueryable glData = factStatisticSampledMonthlyData.Any() ? factStatisticSampledMonthlyData : factStatisticData; + + var batchedDepartments = departments.Batch(1000); + var groupedGlData = new List>(); + foreach (var depts in batchedDepartments) + { + var deptsDictionary = depts.ToDictionary(); + var glGrouping = glData + .Where(gl => deptsDictionary.Keys.Contains(gl.DepartmentID)) + .Where(gl => accounts.Keys.Contains(gl.AccountID)) + .AsEnumerable() + .Where(gl => yearMonthDictionary[gl.FiscalMonthID] == gl.FiscalYearID) + .GroupBy(gl => gl.DepartmentID) + .ToList(); + groupedGlData.AddRange(glGrouping); + } + + var departmentDetails = new List(); + + groupedGlData.ForEach(data => + { + var accountDetails = data.Where(d => d.Value != 0).GroupBy(gl => gl.AccountID).Select(d => new AccountDetail(d.Key, accounts[d.Key], d.Sum(a => a.Value))); + var departmentDetail = new DepartmentDetail(data.Key, departments[data.Key], accountDetails.Sum(d => d.Amount), accountDetails); + departmentDetails.Add(departmentDetail); + }); + + return new GLDetail() { Total = departmentDetails.Sum(d => d.Amount), DepartmentDetails = departmentDetails }; + } + + private async Task> GetDimensionMemberDictionary(string globalId, IEnumerable hierarchyPaths, CancellationToken cancellationToken) + { + var dimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync(globalId); + var result = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, hierarchyPaths, cancellationToken); + var memberDictionary = result.ToDictionary(member => Convert.ToInt32(member.Id), member => member.Name); + + return memberDictionary; + } + + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/IGLData.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/IGLData.cs new file mode 100644 index 0000000..1036605 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/IGLData.cs @@ -0,0 +1,11 @@ +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + public interface IGLData + { + public int DepartmentID { get; set; } + public int AccountID { get; set; } + public short FiscalYearID { get; set; } + public byte FiscalMonthID { get; set; } + public decimal Value { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/IGLDetailService.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/IGLDetailService.cs new file mode 100644 index 0000000..55c1aa2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Details/IGLDetailService.cs @@ -0,0 +1,13 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Details +{ + public interface IGLDetailService : IJazzDbService + { + Task GetOverallDetailAsync(Guid configurationGuid, GLDepartmentAccountData departmentAccountData, CancellationToken cancellationToken); + Task GetStatisticDetailAsync(Guid configurationGuid, GLDepartmentAccountData departmentAccountData, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Initiatives/GLInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Initiatives/GLInitiativeService.cs new file mode 100644 index 0000000..a6531a2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Initiatives/GLInitiativeService.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Logging; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Initiatives +{ + public class GLInitiativeService : InitiativeCreateService, IGLInitiativeService + { + public GLInitiativeService( + IAsyncDbContextFactory dbContextFactory, + IInitiativeSavingsService initiativeSavingsService, + IAuthorizationServiceClient authorizationServiceClient, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger logger) + : base(dbContextFactory, initiativeSavingsService, authorizationServiceClient, updateFactOpportunitySavingsService, logger) + { + } + + public override async Task GetOpportunity(Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + return dbContext.GeneralLedgerOpportunities.SingleOrDefault(opp => opp.OpportunityGuid == opportunityGuid) as IOpportunity; + } + + public override async Task SaveOpportunity(Guid opportunityGuid, Guid initiativeGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunity = dbContext.GeneralLedgerOpportunities.SingleOrDefault(opp => opp.OpportunityGuid == opportunityGuid); + if (opportunity == null) { return; } + + opportunity.InitiativeGuid = initiativeGuid; + await dbContext.SaveChangesAsync(cancellationToken); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Initiatives/GeneralLedgerInitiativeDto.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Initiatives/GeneralLedgerInitiativeDto.cs new file mode 100644 index 0000000..be2d67f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Initiatives/GeneralLedgerInitiativeDto.cs @@ -0,0 +1,21 @@ +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities; +using Strata.ContinuousImprovement.Biz.Initiatives; +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Initiatives +{ + public class GeneralLedgerInitiativeDto : InitiativeDto + { + public Guid ConfigurationGuid { get; set; } + public AccountType GLOpportunityType { get; set; } + public GLOpportunityLevel GoalLevel { get; set; } + public List Departments { get; set; } + public List RevenueAccounts { get; set; } + public List ExpenseAccounts { get; set; } + public List StatisticAccounts { get; set; } + public List StatisticDepartments { get; set; } + public bool HasStatistic { get; set; } + public bool IsSeasonal { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Initiatives/IGLInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Initiatives/IGLInitiativeService.cs new file mode 100644 index 0000000..08dd843 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Initiatives/IGLInitiativeService.cs @@ -0,0 +1,9 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Initiatives +{ + public interface IGLInitiativeService : IInitiativeCreateService + { + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/Filters/GLFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/Filters/GLFilterOptions.cs new file mode 100644 index 0000000..953a0c8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/Filters/GLFilterOptions.cs @@ -0,0 +1,11 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities.Filters +{ + public class GLFilterOptions + { + public IEnumerable Types { get; set; } + public IEnumerable Levels { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/Filters/GLFilters.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/Filters/GLFilters.cs new file mode 100644 index 0000000..96b3316 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/Filters/GLFilters.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities.Filters +{ + public class GLFilters : FiltersOpportunity + { + public IEnumerable Types { get; set; } + public IEnumerable Levels { get; set; } + public SeasonalityType Seasonality { get; set; } + + public GLFilters() : base() + { + Types = new List(); + Levels = new List(); + Seasonality = SeasonalityType.WithAndWithout; + } + + public override IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = base.GetFilters(context).ToList(); + + if (Types.Any()) + filters.Add(m => Types.Contains(m.Type)); + + if (Levels.Any()) + filters.Add(m => Levels.Contains(m.Level)); + + if (Seasonality != SeasonalityType.WithAndWithout) + filters.Add(m => (int)Seasonality == Convert.ToInt32(m.IsSeasonal)); + + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + var testSeasonality = Enum.TryParse(OpportunitySearch, true, out var seasonalityType) + && seasonalityType != SeasonalityType.WithAndWithout; + filters.Add(m => m.OpportunityKey.Contains(OpportunitySearch) + || m.Name.Contains(OpportunitySearch) + || m.Type.Contains(OpportunitySearch) + || m.Level.Contains(OpportunitySearch) + || (testSeasonality && m.IsSeasonal == (seasonalityType == SeasonalityType.With))); + } + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunity.cs new file mode 100644 index 0000000..3db4a1f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunity.cs @@ -0,0 +1,15 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities +{ + [Table("viewGLOpportunity", Schema = "cci")] + public class GLOpportunity : Opportunity + { + public string Name { get; set; } + public long OpportunityId { get; set; } + public string Type { get; set; } + public string Level { get; set; } + public bool IsSeasonal { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunityDataModel.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunityDataModel.cs new file mode 100644 index 0000000..947eaed --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunityDataModel.cs @@ -0,0 +1,82 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Mappers; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities +{ + + [Table("GLOpportunity", Schema = "cci")] + public class GLOpportunityDataModel : IOpportunity, IWithAuthor, IWithOwner + { + public GLOpportunityDataModel() + { + OpportunityGuid = Guid.NewGuid(); + DateCreated = DateTime.Now; + DateModified = DateTime.Now; + } + + [Key] + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public string Note { get; set; } + public DateTime DateCreated { get; set; } + public DateTime DateModified { get; set; } + public Guid OwnerGuid { get; set; } + public string OwnerName { get; set; } + public Guid AuthorGuid { get; set; } + public string AuthorName { get; set; } + public string RevenueDepartmentFilterJSON { get; set; } = "[]"; + public string RevenueAccountFilterJSON { get; set; } = "[]"; + public string ExpenseDepartmentFilterJSON { get; set; } = "[]"; + public string ExpenseAccountFilterJSON { get; set; } = "[]"; + public string StatisticDepartmentFilterJSON { get; set; } = "[]"; + public string StatisticAccountFilterJSON { get; set; } = "[]"; + public AccountType GLOpportunityType { get; set; } + public GLOpportunityLevel GoalLevel { get; set; } + public bool HasStatistic { get; set; } + public bool IsSeasonal { get; set; } + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long OpportunityId { get; set; } + + [NotMapped] + public string OpportunityKey + { + get => $"PR - {OpportunityId}"; set + { // ILB + } + } + [NotMapped] + public double IdentifiedSavings { get; set; } + [NotMapped] + public bool HasInitiative => !InitiativeGuid.Equals(Guid.Empty); + [NotMapped] + public string Initiative { get; set; } + [NotMapped] + public bool IsHidden { get; set; } + [NotMapped] + public string SourceOpportunity { get; set; } + [NotMapped] + public long SourceOpportunityId { get; set; } + + public void Update(IOpportunity updates) + { + throw new NotImplementedException(); + } + } + + public enum GLOpportunityLevel + { + Overall = 0, + Individual = 1 + } + public enum AccountType + { + Revenue = 0, + Expense = 1, + Margin = 2 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunityDataModelMapper.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunityDataModelMapper.cs new file mode 100644 index 0000000..41fb43c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunityDataModelMapper.cs @@ -0,0 +1,60 @@ +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Initiatives; +using Strata.ContinuousImprovement.Biz.Mappers; +using System; +using System.Security.Claims; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class GLOpportunityDataModelMapper + { + public static GLOpportunityDataModel MapToDataModel(this GLOpportunitySaveModel src) + { + var mapped = Mapped(src); + if (mapped == null) return default; + mapped.StatisticDepartmentFilterJSON = src.StatisticDepartments.MapToHierarchyPaths(); + mapped.StatisticAccountFilterJSON = src.StatisticAccounts.MapToHierarchyPaths(); + if (src.GLOpportunityType == AccountType.Revenue || src.GLOpportunityType == AccountType.Margin) + { + mapped.RevenueDepartmentFilterJSON = src.Departments.MapToHierarchyPaths(); + mapped.RevenueAccountFilterJSON = src.RevenueAccounts.MapToHierarchyPaths(); + } + if (src.GLOpportunityType == AccountType.Expense || src.GLOpportunityType == AccountType.Margin) + { + mapped.ExpenseDepartmentFilterJSON = src.Departments.MapToHierarchyPaths(); + mapped.ExpenseAccountFilterJSON = src.ExpenseAccounts.MapToHierarchyPaths(); + } + return mapped; + } + + public static GLOpportunityDataModel MapToDataModel(this GeneralLedgerInitiativeDto src, ClaimsPrincipal user) + { + if (src == null) return default; + var mapped = Mapped(src); + if (mapped == null) return default; + mapped.Note = ""; + mapped.MapAuthor(user); + mapped.MapOwner(user); + mapped.StatisticDepartmentFilterJSON = src.StatisticDepartments.MapToHierarchyPaths(); + mapped.StatisticAccountFilterJSON = src.StatisticAccounts.MapToHierarchyPaths(); + if (src.GLOpportunityType == AccountType.Revenue || src.GLOpportunityType == AccountType.Margin) + { + mapped.RevenueDepartmentFilterJSON = src.Departments.MapToHierarchyPaths(); + mapped.RevenueAccountFilterJSON = src.RevenueAccounts.MapToHierarchyPaths(); + } + if (src.GLOpportunityType == AccountType.Expense || src.GLOpportunityType == AccountType.Margin) + { + mapped.ExpenseDepartmentFilterJSON = src.Departments.MapToHierarchyPaths(); + mapped.ExpenseAccountFilterJSON = src.ExpenseAccounts.MapToHierarchyPaths(); + } + return mapped; + } + + [MapperIgnoreTarget(nameof(GLOpportunityDataModel.OpportunityGuid))] + private static partial GLOpportunityDataModel Mapped(GLOpportunitySaveModel src); + + [MapperIgnoreTarget(nameof(GLOpportunityDataModel.OpportunityGuid))] + private static partial GLOpportunityDataModel Mapped(GeneralLedgerInitiativeDto src); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunitySaveModel.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunitySaveModel.cs new file mode 100644 index 0000000..9cbad92 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunitySaveModel.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities +{ + [ExcludeFromCodeCoverage] + public class GLOpportunitySaveModel + { + [Required] + public Guid ConfigurationGuid { get; set; } + [Required] + [StringLength(200)] + public string Name { get; set; } + public string Note { get; set; } + [Required] + public AccountType GLOpportunityType { get; set; } + [Required] + public GLOpportunityLevel GoalLevel { get; set; } + [Required] + public Guid OwnerGuid { get; set; } + [Required] + public string OwnerName { get; set; } + public Guid AuthorGuid { get; set; } + public string AuthorName { get; set; } + public List Departments { get; set; } + public List RevenueAccounts { get; set; } + public List ExpenseAccounts { get; set; } + public List StatisticAccounts { get; set; } + public List StatisticDepartments { get; set; } + public bool HasStatistic { get; set; } + public bool IsSeasonal { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunityService.cs new file mode 100644 index 0000000..708ac9a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/GLOpportunityService.cs @@ -0,0 +1,144 @@ +using ClosedXML.Excel; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.CoreLib.Claims; +using Strata.Hangfire.Jazz.Client; +using Strata.Hangfire.Jazz.Client.Models; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities +{ + public class GLOpportunityService : OpportunityService, IGLOpportunityService + { + private readonly IJazzHangfireServiceClient _hangfireServiceClient; + private readonly IAuthorizationServiceClient _authorizationServiceClient; + + public GLOpportunityService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IJazzHangfireServiceClient hangfireServiceClient, + IAuthorizationServiceClient authorizationServiceClient, + IInitiativeService initiativeService, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) + { + _hangfireServiceClient = hangfireServiceClient; + _authorizationServiceClient = authorizationServiceClient; + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return null; + + var opportunityContext = dbContext.Set(); + var configuredOpportunities = opportunityContext.Where(en => en.ConfigurationGuid == configurationGuid); + + var glTypes = await configuredOpportunities + .Select(opp => new FilterMember { Id = opp.Type, Text = opp.Type }).Distinct() + .OrderBy(t => t.Text).ToListAsync(cancellationToken); + + var glLevels = await configuredOpportunities + .Select(opp => new FilterMember { Id = opp.Level, Text = opp.Level }).Distinct() + .OrderBy(t => t.Text).ToListAsync(cancellationToken); + + return new GLFilterOptions + { + Types = glTypes, + Levels = glLevels + }; + } + + public async Task ExportAsync(SortOptions sortOptions, GLFilters filters, CancellationToken cancellationToken) + { + var exportOptions = this.GetExportOptions(); + + // get data + var opportunities = await this.GetAllAsync(sortOptions, filters, cancellationToken); + var data = opportunities.Select(opp => new + { + Opportunity = opp.OpportunityKey, + opp.Name, + opp.IdentifiedSavings, + opp.Type, + opp.Level, + IsSeasonal = opp.IsSeasonal ? 1 : 0, + opp.Initiative, + IsHidden = opp.IsHidden ? 1 : 0, + opp.Note + }); + + // configure column formats + exportOptions.AddColumnOptions("Identified Savings", "Currency2Dec"); + exportOptions.AddColumnOptions("Is Seasonal", "\"Yes\";-;\"No\""); + exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\""); + + return ExportUtils.CreateExcelWorkbook(data, exportOptions); + } + + public async Task CreateAsync(GLOpportunitySaveModel saveModel, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + + var opportunity = saveModel.MapToDataModel(); + + await dbContext.GeneralLedgerOpportunitiesDataModel.AddAsync(opportunity, cancellationToken); + var result = await dbContext.SaveChangesAsync(cancellationToken); + + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(opportunity.OpportunityGuid, SecurableEntityType.Opportunity, cancellationToken); + + if (result > 0) + { + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + var job = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.Custom.GeneralLedger.RunGeneralLedgerSavingsTask, Strata.CS.Jazz.Biz.CCI", + ContextData = JsonConvert.SerializeObject(new { opportunityGuid = opportunity.OpportunityGuid }) + }; + + await _hangfireServiceClient.EnqueueJobAsync(job, cancellationToken); + } + + return opportunity.OpportunityGuid; + } + + public async Task CreateAsync(GLOpportunityDataModel opportunity, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + + await dbContext.GeneralLedgerOpportunitiesDataModel.AddAsync(opportunity, cancellationToken); + var result = await dbContext.SaveChangesAsync(cancellationToken); + + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(opportunity.OpportunityGuid, SecurableEntityType.Opportunity, cancellationToken); + + if (result > 0) + { + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + var job = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.Custom.GeneralLedger.RunGeneralLedgerSavingsTask, Strata.CS.Jazz.Biz.CCI", + ContextData = JsonConvert.SerializeObject(new { opportunityGuid = opportunity.OpportunityGuid }) + }; + + var response = await _hangfireServiceClient.EnqueueJobAsync(job, cancellationToken); + return response.JobId; + } + return Guid.Empty; + + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/IGLOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/IGLOpportunityService.cs new file mode 100644 index 0000000..7cea7d6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/GeneralLedger/Opportunities/IGLOpportunityService.cs @@ -0,0 +1,18 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities +{ + public interface IGLOpportunityService : IOpportunityService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + Task ExportAsync(SortOptions sortOptions, GLFilters filters, CancellationToken cancellationToken); + Task CreateAsync(GLOpportunitySaveModel saveModel, CancellationToken cancellationToken); + Task CreateAsync(GLOpportunityDataModel opportunity, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/IJazzDbService.cs b/src/Strata.ContinuousImprovement.Biz/Generics/IJazzDbService.cs new file mode 100644 index 0000000..3383f9c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/IJazzDbService.cs @@ -0,0 +1,33 @@ +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public interface IJazzDbService where T : class + { + Task GetDbContext(CancellationToken cancellationToken); + + Task> GetAllAsync(CancellationToken cancellationToken); + + Task> GetAllAsync(SortOptions sortOptions, IFilters filters, CancellationToken cancellationToken); + + Task> GetPagedAsync(PagingOptions pagingOptions, IFilters filters, CancellationToken cancellationToken); + + Task GetById(TKey id, CancellationToken cancellationToken); + + Task GetByInitiativeId(TKey id, Guid configurationGuid, OpportunityTypes opportunityType, CancellationToken cancellationToken); + Task GetOpportunityByNamePrefix(Guid configurationGuid, OpportunityTypes opportunityType, string name, CancellationToken cancellationToken); + + Task Contains(T entity, CancellationToken cancellationToken); + Task Contains(TKey id, CancellationToken cancellationToken); + + Task> GetByIds(IEnumerable ids, CancellationToken cancellationToken); + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/IOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Generics/IOpportunity.cs new file mode 100644 index 0000000..52281b6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/IOpportunity.cs @@ -0,0 +1,18 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public interface IOpportunity : IWithConfigurationGuid, IWithSourceOpportunity + { + Guid OpportunityGuid { get; set; } + string OpportunityKey { get; set; } + double IdentifiedSavings { get; set; } + Guid InitiativeGuid { get; set; } + bool HasInitiative { get; } + string Initiative { get; set; } + bool IsHidden { get; set; } + string Note { get; set; } + + abstract void Update(IOpportunity updates); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/IOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Generics/IOpportunityService.cs new file mode 100644 index 0000000..0c3c9eb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/IOpportunityService.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public interface IOpportunityService : IJazzDbService + where T : class, IOpportunity + { + Task SetHiddenAsync(Guid opportunityGuid, bool isHidden, CancellationToken cancellationToken); + + Task SetNoteAsync(Guid opportunityGuid, string note, CancellationToken cancellationToken); + + Task DeleteAsync(Guid opportunityGuid, CancellationToken cancellationToken) + where Entity : class, IOpportunity; + } + +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/IUpdateFactOpportunitySavingsService.cs b/src/Strata.ContinuousImprovement.Biz/Generics/IUpdateFactOpportunitySavingsService.cs new file mode 100644 index 0000000..7695d00 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/IUpdateFactOpportunitySavingsService.cs @@ -0,0 +1,10 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public interface IUpdateFactOpportunitySavingsService + { + Task UpdateFactOpportunitySavings(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/IWithConfigurationGuid.cs b/src/Strata.ContinuousImprovement.Biz/Generics/IWithConfigurationGuid.cs new file mode 100644 index 0000000..daa0e6b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/IWithConfigurationGuid.cs @@ -0,0 +1,9 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public interface IWithConfigurationGuid + { + Guid ConfigurationGuid { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/IWithIdentifiedSavingsTotal.cs b/src/Strata.ContinuousImprovement.Biz/Generics/IWithIdentifiedSavingsTotal.cs new file mode 100644 index 0000000..44fa3f7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/IWithIdentifiedSavingsTotal.cs @@ -0,0 +1,7 @@ +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public interface IWithIdentifiedSavingsTotal + { + public double IdentifiedSavingsTotal { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/IWithSourceOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Generics/IWithSourceOpportunity.cs new file mode 100644 index 0000000..b6f59d5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/IWithSourceOpportunity.cs @@ -0,0 +1,17 @@ +using Strata.ContinuousImprovement.Biz.Network; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public interface IWithSourceOpportunity + { + string SourceOpportunity { get; set; } + long SourceOpportunityId { get; set; } + + public virtual void SetSourceOpportunity(ClientNetworkOpportunity sourceOpportunity) + { + if (sourceOpportunity == null) { return; } + SourceOpportunity = sourceOpportunity.NetworkOpportunity.Name; + SourceOpportunityId = sourceOpportunity.NetworkOpportunityId; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/IWithStrataId.cs b/src/Strata.ContinuousImprovement.Biz/Generics/IWithStrataId.cs new file mode 100644 index 0000000..737667e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/IWithStrataId.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public interface IWithStrataId + { + public int StrataId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/JazzDbService.cs b/src/Strata.ContinuousImprovement.Biz/Generics/JazzDbService.cs new file mode 100644 index 0000000..eb81997 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/JazzDbService.cs @@ -0,0 +1,276 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public abstract class JazzDbService : IJazzDbService where T : class + { + protected readonly IAsyncDbContextFactory _dbContextFactory; + protected readonly CentralDbContext _centralDbContext; + protected readonly ClaimsPrincipal _user; + + protected JazzDbService(IAsyncDbContextFactory dbContextFactory, CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor) + { + _dbContextFactory = dbContextFactory; + _centralDbContext = centralDbContext; + _user = claimsPrincipalAccessor?.GetCurrentClaimsPrincipal(); + } + + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + /// + /// Get all entities in table + /// + public virtual async Task> GetAllAsync(CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return new List(); + var query = dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user); + + var results = await query.ToListAsync(cancellationToken); + if (_centralDbContext != null) + { + foreach (var opportunity in results.Where(opp => opp is IWithSourceOpportunity && opp is IOpportunity)) + { + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities.Where(link => link.OpportunityGuid == ((IOpportunity)opportunity).OpportunityGuid)) + .SingleOrDefaultAsync(cancellationToken); + if (clientNetworkOpportunity != null) + { + ((IWithSourceOpportunity)opportunity).SetSourceOpportunity(clientNetworkOpportunity); + } + } + } + return results; + } + + /// + /// Get all entities in table with filters + /// + public virtual async Task> GetAllAsync(SortOptions sortOptions, IFilters filters, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null || filters == null) return new List(); + + var query = dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user).OrderBy(sortOptions); + var results = await query.Where(filters.GetFilters(dbContext)).ToListAsync(cancellationToken); + var opportunities = results.Where(data => data is IWithSourceOpportunity && data is IOpportunity).ToList(); + if (opportunities.Any() && _centralDbContext != null) + { + foreach (var opportunity in results.Where(opp => opp is IWithSourceOpportunity && opp is IOpportunity)) + { + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities) + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(cno => cno.Opportunities.Any(link => link.OpportunityGuid == ((IOpportunity)opportunity).OpportunityGuid)); + if (clientNetworkOpportunity != null) + { + ((IWithSourceOpportunity)opportunity).SetSourceOpportunity(clientNetworkOpportunity); + } + } + } + return results; + } + + /// + /// Get entities in table with filters and paging + /// + public virtual async Task> GetPagedAsync(PagingOptions pagingOptions, IFilters filters, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return new PagedApiResponse() { Data = new List(), PagingInfo = new PagingInfo() }; + + var query = dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user); + + var results = await query.Where(filters.GetFilters(dbContext)).ToPagedAsync(pagingOptions, cancellationToken); + var opportunities = results.Data.Where(data => data is IWithSourceOpportunity && data is IOpportunity).ToList(); + if (opportunities.Any() && _centralDbContext != null) + { + foreach (var opportunity in results.Data.Where(opp => opp is IWithSourceOpportunity && opp is IOpportunity)) + { + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities) + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(cno => cno.Opportunities.Any(link => link.OpportunityGuid == ((IOpportunity)opportunity).OpportunityGuid)); + if (clientNetworkOpportunity != null) + { + ((IWithSourceOpportunity)opportunity).SetSourceOpportunity(clientNetworkOpportunity); + } + } + } + return results; + } + + /// + /// Get entity by primary key value + /// + public virtual async Task GetById(TKey id, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return default; + var keyProperty = dbContext.Model.FindEntityType(typeof(T)) + .FindPrimaryKey().Properties.Single().Name; + var result = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .FirstOrDefaultAsync(x => id.Equals(EF.Property(x, keyProperty)), cancellationToken); + var opportunity = result as IOpportunity; + if (opportunity != null && opportunity is IWithSourceOpportunity && _centralDbContext != null) + { + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities) + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(cno => cno.Opportunities.Any(link => link.OpportunityGuid == opportunity.OpportunityGuid)); + if (clientNetworkOpportunity != null) + { + opportunity.SetSourceOpportunity(clientNetworkOpportunity); + } + } + return result; + } + + public virtual async Task GetByInitiativeId(TKey id, Guid configurationGuid, OpportunityTypes opportunityType, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return default; + var entity = dbContext.Model.FindEntityType(typeof(T)); + var initiativeGuid = entity.GetProperties() + .SingleOrDefault(p => p.Name.Equals(nameof(Initiatives.Initiative.InitiativeGUID), StringComparison.CurrentCultureIgnoreCase))?.Name; + if (initiativeGuid == null) return default; + var configurationGuidName = entity.GetProperties() + .SingleOrDefault(p => p.Name.Equals(nameof(AllOpportunity.ConfigurationGuid), StringComparison.CurrentCultureIgnoreCase))?.Name; + if (configurationGuidName == null) return default; + var opportunityTypeName = entity.GetProperties() + .SingleOrDefault(p => p.Name.Equals(nameof(AllOpportunity.OpportunityType), StringComparison.CurrentCultureIgnoreCase))?.Name; + if (opportunityTypeName == null) return default; + var result = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .FirstOrDefaultAsync(x => EF.Property(x, initiativeGuid).Equals(id) + && EF.Property(x, configurationGuidName).Equals(configurationGuid) + && EF.Property(x, opportunityTypeName).Equals((int)opportunityType), cancellationToken); + return result; + } + + public virtual async Task GetOpportunityByNamePrefix(Guid configurationGuid, OpportunityTypes opportunityType, string name, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return default; + var entity = dbContext.Model.FindEntityType(typeof(T)); + var initiativeName = entity.GetProperties() + .SingleOrDefault(p => p.Name.Equals("Name", StringComparison.CurrentCultureIgnoreCase))?.Name; + if (string.IsNullOrEmpty(initiativeName)) return default; + var configurationGuidName = entity.GetProperties() + .SingleOrDefault(p => p.Name.Equals(nameof(AllOpportunity.ConfigurationGuid), StringComparison.CurrentCultureIgnoreCase))?.Name; + if (configurationGuidName == null) return default; + var opportunityTypeName = entity.GetProperties() + .SingleOrDefault(p => p.Name.Equals(nameof(AllOpportunity.OpportunityType), StringComparison.CurrentCultureIgnoreCase))?.Name; + if (opportunityTypeName == null) return default; + var result = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .FirstOrDefaultAsync(x => EF.Property(x, initiativeName).StartsWith(name) + && EF.Property(x, configurationGuidName).Equals(configurationGuid) + && EF.Property(x, opportunityTypeName).Equals((int)opportunityType), cancellationToken); + return result; + } + + public virtual async Task Contains(T entity, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return default; + var keyProperty = dbContext.Model.FindEntityType(typeof(T)) + .FindPrimaryKey().Properties.Single().Name; + var id = entity.GetType().GetProperty(keyProperty).GetValue(entity, null); + var result = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .FirstOrDefaultAsync(x => id.Equals(EF.Property(x, keyProperty)), cancellationToken); + return result != null; + } + + public virtual async Task Contains(TKey id, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return default; + var keyProperty = dbContext.Model.FindEntityType(typeof(T)) + .FindPrimaryKey().Properties.Single().Name; + var entity = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .FirstOrDefaultAsync(x => id.Equals(EF.Property(x, keyProperty)), cancellationToken); + return entity != null; + } + + protected virtual async Task SetSourceOpportunity(IWithSourceOpportunity opportunity, CancellationToken cancellationToken) + { + if (opportunity == null || opportunity is not IOpportunity || opportunity is not IWithSourceOpportunity || _centralDbContext == null) + return opportunity; + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities) + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(cno => cno.Opportunities.Any(link => link.OpportunityGuid == ((IOpportunity)opportunity).OpportunityGuid)); + if (clientNetworkOpportunity != null) + { + opportunity.SetSourceOpportunity(clientNetworkOpportunity); + } + else + { + opportunity.SourceOpportunityId = -1; + } + return opportunity; + } + + /// + /// Get entities by primary key values + /// + public virtual Task> GetByIds(IEnumerable ids, CancellationToken cancellationToken) + { + // prevent long IN clauses + if (ids.Count() > 2000) + { + throw new ArgumentException("List of ids cannot exceed 2000 count."); + } + + return GetByIdsInternal(ids, cancellationToken); + } + + /// + /// Get entities by primary key values + /// + private async Task> GetByIdsInternal(IEnumerable ids, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return new List(); + var keyProperty = dbContext.Model.FindEntityType(typeof(T)) + .FindPrimaryKey().Properties.Single().Name; + var results = await dbContext.Set() + .Where(x => ids.Contains(EF.Property(x, keyProperty))) + .ToListAsync(cancellationToken); + var opportunities = results.Where(data => data is IWithSourceOpportunity && data is IOpportunity).ToList(); + if (opportunities.Any() && _centralDbContext != null) + { + foreach (var opportunity in results.Where(opp => opp is IWithSourceOpportunity && opp is IOpportunity)) + { + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities) + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(cno => cno.Opportunities.Any(link => link.OpportunityGuid == ((IOpportunity)opportunity).OpportunityGuid)); + if (clientNetworkOpportunity != null) + { + ((IWithSourceOpportunity)opportunity).SetSourceOpportunity(clientNetworkOpportunity); + } + } + } + return results; + } + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/Opportunity.cs b/src/Strata.ContinuousImprovement.Biz/Generics/Opportunity.cs new file mode 100644 index 0000000..606cd22 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/Opportunity.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public class Opportunity : IOpportunity + { + [Key] public Guid OpportunityGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public string OpportunityKey { get; set; } + public double IdentifiedSavings { get; set; } + public Guid InitiativeGuid { get; set; } + public bool HasInitiative => InitiativeGuid != Guid.Empty; + public string Initiative { get; set; } + public bool IsHidden { get; set; } + public string Note { get; set; } = String.Empty; + + [NotMapped] + public string SourceOpportunity { get; set; } = ""; + [NotMapped] + public long SourceOpportunityId { get; set; } = -1; + + public virtual void Update(IOpportunity updates) + { + IdentifiedSavings = updates.IdentifiedSavings; + InitiativeGuid = updates.InitiativeGuid; + IsHidden = updates.IsHidden; + Note = updates.Note; + } + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/OpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Generics/OpportunityService.cs new file mode 100644 index 0000000..f83a8a2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/OpportunityService.cs @@ -0,0 +1,140 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public abstract class OpportunityService : JazzDbService, IOpportunityService + where T : class, IOpportunity + { + protected readonly IInitiativeService _initiativeService; + protected readonly IUpdateFactOpportunitySavingsService _updateFactOpportunitySavingsService; + protected readonly ILogger> _logger; + + protected OpportunityService(IAsyncDbContextFactory dbContextFactory, CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IInitiativeService initiativeService, IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger> logger = default) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor) + { + _initiativeService = initiativeService; + _updateFactOpportunitySavingsService = updateFactOpportunitySavingsService; + _logger = logger; + } + + protected ExportOptions GetExportOptions() + { + return new ExportOptions() { EmptyMessage = "No Opportunities" }; + } + + public virtual async Task SetHiddenAsync(Guid opportunityGuid, bool isHidden, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return; + var opportunityItem = await dbContext.Set() + .SingleOrDefaultAsync(vo => vo.OpportunityGuid.Equals(opportunityGuid), cancellationToken); + if (opportunityItem == null) return; + + if (typeof(T).Name == "UVOpportunity") + { + if (opportunityItem.OpportunityKey.StartsWith("FE")) + { + var explorationOpportunity = await dbContext.ExplorationVariationOpportunitiesData + .SingleOrDefaultAsync(vo => vo.OpportunityGuid.Equals(opportunityGuid), cancellationToken); + if (explorationOpportunity == null) return; + explorationOpportunity.IsHidden = isHidden; + await dbContext.SaveChangesAsync(cancellationToken); + } + else if (opportunityItem.OpportunityKey.StartsWith("UV")) + { + var variationOpportunity = await dbContext.UtilizationVariationOpportunitiesData + .SingleOrDefaultAsync(vo => vo.OpportunityGuid.Equals(opportunityGuid), cancellationToken); + if (variationOpportunity == null) return; + variationOpportunity.IsHidden = isHidden; + await dbContext.SaveChangesAsync(cancellationToken); + } + } + else + { + ((IOpportunity)opportunityItem).IsHidden = isHidden; + await dbContext.SaveChangesAsync(cancellationToken); + } + } + + public virtual async Task SetNoteAsync(Guid opportunityGuid, string note, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return; + var opportunityItem = await dbContext.Set() + .SingleOrDefaultAsync(vo => vo.OpportunityGuid.Equals(opportunityGuid), cancellationToken); + if (opportunityItem == null) return; + + if (typeof(T).Name == "UVOpportunity") + { + if (opportunityItem.OpportunityKey.StartsWith("FE")) + { + var explorationOpportunity = await dbContext.ExplorationVariationOpportunitiesData + .SingleOrDefaultAsync(vo => vo.OpportunityGuid.Equals(opportunityGuid), cancellationToken); + if (explorationOpportunity == null) return; + explorationOpportunity.Note = note; + await dbContext.SaveChangesAsync(cancellationToken); + } + else if (opportunityItem.OpportunityKey.StartsWith("UV")) + { + var variationOpportunity = await dbContext.UtilizationVariationOpportunitiesData + .SingleOrDefaultAsync(vo => vo.OpportunityGuid.Equals(opportunityGuid), cancellationToken); + if (variationOpportunity == null) return; + variationOpportunity.Note = note; + await dbContext.SaveChangesAsync(cancellationToken); + } + } + else + { + ((IOpportunity)opportunityItem).Note = note; + await dbContext.SaveChangesAsync(cancellationToken); + } + } + + public virtual async Task DeleteAsync(Guid opportunityGuid, CancellationToken cancellationToken) + where Entity : class, IOpportunity + { + using var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return false; + + var result = 0; + try + { + var opportunity = await dbContext.Set().SingleOrDefaultAsync(opp => opp.OpportunityGuid.Equals(opportunityGuid), cancellationToken); + if (opportunity == null) return false; + + if (opportunity.InitiativeGuid != Guid.Empty) + { + await _initiativeService.DeleteAsync(opportunity.InitiativeGuid, cancellationToken); + } + + dbContext.Set().Remove(opportunity); + result = await dbContext.SaveChangesAsync(cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + } + catch (Exception ex) + { + if (_logger == default) return false; + _logger.LogServiceError(ex, ex.Message, new Dictionary + { + { nameof(opportunityGuid), opportunityGuid } + }); + } + return result > 0; + } + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/OpportunityTypes.cs b/src/Strata.ContinuousImprovement.Biz/Generics/OpportunityTypes.cs new file mode 100644 index 0000000..14ccaad --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/OpportunityTypes.cs @@ -0,0 +1,17 @@ +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public enum OpportunityTypes : byte + { + Variation, + QualityVariation = 2, + OrgDefined = 4, + FreeForm, + GeneralLedger, + Payroll, + Encounter, + ChargeCode, + Network, + Exploration, + Blank + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/StrategicOpportunityTypes.cs b/src/Strata.ContinuousImprovement.Biz/Generics/StrategicOpportunityTypes.cs new file mode 100644 index 0000000..ada5ce6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/StrategicOpportunityTypes.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public enum StrategicOpportunityTypes : byte + { + ChargeCode, + GeneralLedger, + Encounter + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Generics/UpdateFactOpportunitySavingsService.cs b/src/Strata.ContinuousImprovement.Biz/Generics/UpdateFactOpportunitySavingsService.cs new file mode 100644 index 0000000..fd9efc9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Generics/UpdateFactOpportunitySavingsService.cs @@ -0,0 +1,45 @@ +using DocumentFormat.OpenXml.Wordprocessing; +using Hangfire; +using Hangfire.Common; +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Hangfire; +using Strata.ContinuousImprovement.Biz.Replication; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.Hangfire.Jazz.Client; +using Strata.Hangfire.Jazz.Client.Models; +using Strata.Id.Client; +using Strata.Replication.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + +namespace Strata.ContinuousImprovement.Biz.Generics +{ + public class UpdateFactOpportunitySavingsService : IUpdateFactOpportunitySavingsService + { + private readonly IJazzHangfireServiceClient _hangfireServiceClient; + + public UpdateFactOpportunitySavingsService( + IJazzHangfireServiceClient jazzHangfireServiceClient) + { + _hangfireServiceClient = jazzHangfireServiceClient; + } + + public async Task UpdateFactOpportunitySavings(CancellationToken cancellationToken) + { + var job = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.RunUpdateFactOpportunitySavingsTask, Strata.CS.Jazz.Biz.CCI" + }; + await _hangfireServiceClient.EnqueueJobAsync(job, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Hangfire/AccessGudidSection.cs b/src/Strata.ContinuousImprovement.Biz/Hangfire/AccessGudidSection.cs new file mode 100644 index 0000000..2ed5043 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Hangfire/AccessGudidSection.cs @@ -0,0 +1,7 @@ +namespace Strata.ContinuousImprovement.Biz.Hangfire +{ + public class AccessGudidSection + { + public string Url { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Hangfire/HangfireSection.cs b/src/Strata.ContinuousImprovement.Biz/Hangfire/HangfireSection.cs new file mode 100644 index 0000000..b7bcb9e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Hangfire/HangfireSection.cs @@ -0,0 +1,7 @@ +namespace Strata.ContinuousImprovement.Biz.Hangfire +{ + public class HangfireSection + { + public string Url { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Hangfire/HangfireUtility.cs b/src/Strata.ContinuousImprovement.Biz/Hangfire/HangfireUtility.cs new file mode 100644 index 0000000..0908c79 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Hangfire/HangfireUtility.cs @@ -0,0 +1,50 @@ +using Hangfire; +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Hangfire +{ + public class HangfireUtility + { + private readonly IAsyncDbContextFactory _dbContextFactory; + + public HangfireUtility(IAsyncDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + [DeleteOnSuccess] + [JobDisplayName("Run Jazz Hangfire Task {2} on ({0})")] + [CaptureJobParameter(DatabaseGuidArgumentPosition = 1, StrataIdArgumentPosition = 0)] + public async Task RunJazzHangfireTaskJob(int strataId, Guid databaseGuid, string taskName, string qualifiedName, object context, CancellationToken cancellationToken) + { + using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + var contextJson = JsonSerializer.Serialize(context, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }); + var command = new StringBuilder(); + command.AppendFormat("declare @TaskName varchar(max) = '{0}';\r\n", taskName.SplitCamelCase()); + command.AppendFormat("declare @AssemblyQualifiedName varchar(max) = 'Strata.CS.Jazz.Biz.CCI.{0}, " + + "Strata.CS.Jazz.Biz.CCI, Version=2015.20.0.0, Culture=neutral, PublicKeyToken=null';\r\n", qualifiedName); + command.AppendLine("DECLARE @statusCode INT;"); + command.AppendLine("EXEC dbo.procCreateTask"); + command.AppendLine(" @taskTypeName = @AssemblyQualifiedName,"); + command.AppendFormat(" @contextXML = '{0}',\r\n", contextJson == null ? "" : $"{contextJson.Replace("{", "{{").Replace("}", "}}")}"); + command.AppendLine(" @userName = 'HangfireMigration', -- varchar(100)"); + command.AppendLine(" @taskSource = 'Microservice',"); + command.AppendLine(" @statusCode = @statusCode OUTPUT -- int"); + var sql = command.ToString(); + return await dbContext.Database.ExecuteSqlRawAsync(sql, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Hangfire/ISprintRefresh.cs b/src/Strata.ContinuousImprovement.Biz/Hangfire/ISprintRefresh.cs new file mode 100644 index 0000000..040a45c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Hangfire/ISprintRefresh.cs @@ -0,0 +1,10 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Hangfire +{ + public interface ISprintRefresh + { + Task CommentsRefreshJob(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Hangfire/SprintRefresh.cs b/src/Strata.ContinuousImprovement.Biz/Hangfire/SprintRefresh.cs new file mode 100644 index 0000000..7dfa7b0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Hangfire/SprintRefresh.cs @@ -0,0 +1,121 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.OpportunityComments; +using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Id.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Hangfire +{ + public class SprintRefresh : ISprintRefresh + { + private readonly CentralDbContext _centralDbContext; + private readonly IIdServiceClient _idServiceClient; + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IRecurringJobManager _recurringJobManager; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly ILogger _logger; + + public SprintRefresh(CentralDbContext centralDbContext, + IIdServiceClient idServiceClient, + IAsyncDbContextFactory dbContextFactory, + IRecurringJobManager recurringJobManager, + IBackgroundJobClient backgroundJobClient, + ILogger logger) + { + _centralDbContext = centralDbContext; + _idServiceClient = idServiceClient; + _dbContextFactory = dbContextFactory; + _recurringJobManager = recurringJobManager; + _backgroundJobClient = backgroundJobClient; + _logger = logger; + } + + [DeleteOnSuccess] + [JobDisplayName("Sprint Rollover Refresh")] + [Semaphore(nameof(SprintRefreshJob))] + public async Task SprintRefreshJob(CancellationToken cancellationToken) + { + _recurringJobManager.Trigger(nameof(SprintRefresh.CommentsRefreshJob)); + _recurringJobManager.Trigger(nameof(SprintRefresh.OpportunityWorkbooksRefreshJob)); + _recurringJobManager.Trigger(nameof(SprintRefresh.NetworkOpportunityRefreshJob)); + _recurringJobManager.Trigger(nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob) + "DevMaster"); + _recurringJobManager.Trigger(nameof(SprintRefresh.RunUpdateFactOpportunitySavingsTaskJob) + "QaMaster"); + } + + [DeleteOnSuccess] + [JobDisplayName("Sprint Rollover Comments Refresh")] + [Semaphore(nameof(CommentsRefreshJob))] + public async Task CommentsRefreshJob(CancellationToken cancellationToken) + { + await RefreshJob(cancellationToken); + } + + [DeleteOnSuccess] + [JobDisplayName("Sprint Rollover Opportunity Workbooks Refresh")] + [Semaphore(nameof(OpportunityWorkbooksRefreshJob))] + public async Task OpportunityWorkbooksRefreshJob(CancellationToken cancellationToken) + { + await RefreshJob(cancellationToken); + } + + [DeleteOnSuccess] + [JobDisplayName("Sprint Rollover Network Opportunity Refresh")] + [Semaphore(nameof(NetworkOpportunityRefreshJob))] + public async Task NetworkOpportunityRefreshJob(CancellationToken cancellationToken) + { + await RefreshJob(cancellationToken); + } + + [DeleteOnSuccess] + [JobDisplayName("Sprint Rollover Update Fact Opportunity Savings Refresh on {0}")] + [Semaphore(nameof(RunUpdateFactOpportunitySavingsTaskJob))] + public async Task RunUpdateFactOpportunitySavingsTaskJob(string databaseName, CancellationToken cancellationToken) + { + var clients = await _idServiceClient.GetStrataIds(cancellationToken); + var db = clients.SingleOrDefault(client => client.DatabaseName.Equals(databaseName, StringComparison.CurrentCultureIgnoreCase)); + if (db == null) { return; } + var hangfireUtility = new HangfireUtility(_dbContextFactory); + _backgroundJobClient.Enqueue(job => + hangfireUtility.RunJazzHangfireTaskJob(db.StrataId, db.DatabaseGUID, + "RunUpdateFactOpportunitySavingsTask", "Tasks.RunUpdateFactOpportunitySavingsTask", + new { RunContext = Guid.NewGuid() }, cancellationToken)); + } + + private async Task RefreshJob(CancellationToken cancellationToken) where T : class + { + try + { + var count = await _centralDbContext.Set().CountAsync(cancellationToken); + var tableName = _centralDbContext.SchemaQualifiedTableName(); + if (count == 0) + { + _logger.LogInformation("No {tableName} to be removed", tableName); + return; + } + var truncateSql = _centralDbContext.TruncateSql(); + await _centralDbContext.Database.ExecuteSqlRawAsync(truncateSql); + var removedCount = await _centralDbContext.Set().CountAsync(cancellationToken); + var removed = count - removedCount; + _logger.LogInformation("Removing {count} from {tableName}, {removed} removed", tableName, count, removed); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + throw; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Hangfire/SprintRefreshExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Hangfire/SprintRefreshExtensions.cs new file mode 100644 index 0000000..9194863 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Hangfire/SprintRefreshExtensions.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Utilities; + +namespace Strata.ContinuousImprovement.Biz.Hangfire +{ + public static class SprintRefreshExtensions + { + public static string SchemaQualifiedTableName(this DbContext dbContext) + { + var entityType = dbContext.Model.FindEntityType(typeof(T)); + var schemaTableName = entityType.GetSchemaQualifiedTableName(); + return schemaTableName.Replace(".", " ").SplitCamelCase(); + } + + public static string TruncateSql(this DbContext dbContext) + { + var entityType = dbContext.Model.FindEntityType(typeof(T)); + var schemaTableName = entityType.GetSchemaQualifiedTableName(); + return $"TRUNCATE {schemaTableName} RESTART IDENTITY CASCADE;"; + } + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Hubs/HangfireHub.cs b/src/Strata.ContinuousImprovement.Biz/Hubs/HangfireHub.cs new file mode 100644 index 0000000..f9eb744 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Hubs/HangfireHub.cs @@ -0,0 +1,87 @@ +using Microsoft.AspNetCore.SignalR; +using System; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Hubs +{ + 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 + { + 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 context, object objectId, object payload) + { + return context.SendStatus(HangfireHub.InfoStatus, objectId, payload); + } + + public static Task SendSuccess(this IHubContext context, object objectId, object payload) + { + return context.SendStatus(HangfireHub.SuccessStatus, objectId, payload); + } + + public static Task SendFailure(this IHubContext context, object objectId, Exception exception) + { + return context.SendStatus(HangfireHub.ErrorStatus, objectId, exception.Message); + } + + public static Task SendFailure(this IHubContext context, object objectId, object payload) + { + return context.SendStatus(HangfireHub.ErrorStatus, objectId, payload); + } + + public static Task SendStatus(this IHubContext 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 context, string topic, object objectId, object payload) + { + return context.SendStatusToGroup(topic, HangfireHub.InfoStatus, objectId, payload); + } + + public static Task SendSuccessToGroup(this IHubContext context, string topic, object objectId, object payload) + { + return context.SendStatusToGroup(topic, HangfireHub.SuccessStatus, objectId, payload); + } + + public static Task SendFailureToGroup(this IHubContext context, string topic, object objectId, Exception exception) + { + return context.SendStatusToGroup(topic, HangfireHub.ErrorStatus, objectId, exception.Message); + } + + public static Task SendFailureToGroup(this IHubContext context, string topic, object objectId, object payload) + { + return context.SendStatusToGroup(topic, HangfireHub.ErrorStatus, objectId, payload); + } + + public static Task SendStatusToGroup(this IHubContext context, string topic, string status, object objectId, object payload) + { + return context.Clients.Group(topic).SendHangfireStatus(status, JsonSerializer.Serialize(objectId), JsonSerializer.Serialize(payload)); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Hubs/NotifyJobFilter.cs b/src/Strata.ContinuousImprovement.Biz/Hubs/NotifyJobFilter.cs new file mode 100644 index 0000000..853b367 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Hubs/NotifyJobFilter.cs @@ -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.ContinuousImprovement.Biz.Hubs +{ + public class NotifyJobFilter : JobFilterAttribute, IApplyStateFilter + { + private readonly ILogger _logger; + private readonly IHubContext _hangfireHubContext; + + public NotifyJobFilter([NotNull] ILogger logger, [NotNull] IHubContext 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) + { + + } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeDetailColumns/IInitiativeDetailColumnService.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeDetailColumns/IInitiativeDetailColumnService.cs new file mode 100644 index 0000000..fd8d34f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeDetailColumns/IInitiativeDetailColumnService.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.InitiativeDetailColumns +{ + public interface IInitiativeDetailColumnService + { + Task> GetColumnList(Guid userGuid, CancellationToken cancellationToken); + Task> GetManagedColumnList(Guid userGuid, CancellationToken cancellationToken); + IEnumerable GetDefaultColumnList(); + Task SetColumnList(Guid userGuid, IEnumerable columnList, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeDetailColumns/InitiativeDetailColumn.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeDetailColumns/InitiativeDetailColumn.cs new file mode 100644 index 0000000..d256c37 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeDetailColumns/InitiativeDetailColumn.cs @@ -0,0 +1,88 @@ +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Strata.ContinuousImprovement.Biz.InitiativeDetailColumns +{ + public class InitiativeDetailColumn + { + public int Index { get; set; } + public string Name { get; set; } + public string Heading { get; set; } + public bool IsLocked { get; set; } + public bool IsHidden { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public int? InitiativeRollupColumnId { get; set; } + + public InitiativeDetailColumn() + { + + } + + public InitiativeDetailColumn(PropertyInfo propertyInfo, int index, bool isLocked) + { + Index = index; + Name = propertyInfo.Name; + switch (propertyInfo.Name) + { + case nameof(InitiativeDetail.Name): + Heading = "Initiative"; break; + case nameof(Initiative.SavingsCommitted): + Heading = "12-Month Goal Savings"; break; + case nameof(Initiative.AnnualGoal): + Heading = "Committed FY Savings"; break; + default: + Heading = Name.SplitCamelCase(); break; + } + IsLocked = isLocked; + if (propertyInfo.Name.StartsWith("InitiativeRollup", StringComparison.CurrentCultureIgnoreCase)) + { + InitiativeRollupColumnId = int.TryParse(Regex.Replace(propertyInfo.Name, @"([\d])", "$1"), out var result) ? result : null; + } + } + + public static IEnumerable ManagedColumnsToIgnore + => new[] { + nameof(InitiativeDetail.InitiativeRollup1Id), + nameof(InitiativeDetail.InitiativeRollup2Id), + nameof(InitiativeDetail.InitiativeRollup3Id) + }; + + public static IEnumerable ColumnsToIgnore + => new[] { + nameof(InitiativeDetail.InitiativeGuid), + nameof(InitiativeDetail.OpportunityType), + nameof(InitiativeDetail.InitiativeRollup1), + nameof(InitiativeDetail.InitiativeRollup2), + nameof(InitiativeDetail.InitiativeRollup3), + nameof(InitiativeDetail.GoalStatement), + nameof(InitiativeDetail.PlannedImprovements), + nameof(InitiativeDetail.CurrentMonth), + nameof(InitiativeDetail.Stakeholders), + nameof(InitiativeDetail.TeamMembers), + nameof(InitiativeDetail.InitiativeStatus), + nameof(InitiativeDetail.SourceOpportunityId), + nameof(InitiativeDetail.OpportunityKey), + nameof(InitiativeDetail.OpportunityId), + nameof(InitiativeDetail.Comments), + nameof(InitiativeDetail.Milestones), + nameof(InitiativeDetail.CurrentStep) + }; + + public static IEnumerable GetListOfColumns() + { + var columnsToIgnore = ColumnsToIgnore; + var lockedColumns = new[] { nameof(InitiativeDetail.Name) }; + var properties = typeof(InitiativeDetail).GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => !columnsToIgnore.Contains(p.Name)) + .Select((p, i) => new InitiativeDetailColumn(p, i, lockedColumns.Contains(p.Name))); + return properties; + } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeDetailColumns/InitiativeDetailColumnService.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeDetailColumns/InitiativeDetailColumnService.cs new file mode 100644 index 0000000..ce51fba --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeDetailColumns/InitiativeDetailColumnService.cs @@ -0,0 +1,61 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.UserSetting; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.InitiativeDetailColumns +{ + public class InitiativeDetailColumnService : IInitiativeDetailColumnService + { + private readonly IUserSettingService _userSettingService; + + public InitiativeDetailColumnService(IUserSettingService userSettingService) + { + _userSettingService = userSettingService; + } + + public async Task> GetColumnList(Guid userGuid, CancellationToken cancellationToken) + { + var columns = await GetColumnsAsync(userGuid, cancellationToken); + return columns; + } + + public async Task> GetManagedColumnList(Guid userGuid, CancellationToken cancellationToken) + { + var columns = await GetColumnsAsync(userGuid, cancellationToken); + return columns.Where(cl => !InitiativeDetailColumn.ManagedColumnsToIgnore.Contains(cl.Name, StringComparer.OrdinalIgnoreCase)); + } + + private async Task> GetColumnsAsync(Guid userGuid, CancellationToken cancellationToken) + { + var columnsToIgnore = InitiativeDetailColumn.ColumnsToIgnore.ToList(); + var defaultColumnList = GetDefaultColumnList(); + var columnList = await _userSettingService.GetDboUserSettings(DboUserSetting.CCI_INITIATIVES_COLUMNS, userGuid, cancellationToken); + var result = columnList.Any() ? columnList.Where(col => !columnsToIgnore.Contains(col.Name)).ToList() : defaultColumnList; + return result.OrderBy(r => r.Index); + } + + public IEnumerable GetDefaultColumnList() + { + var defaultColumnList = InitiativeDetailColumn.GetListOfColumns().ToList(); + return defaultColumnList; + } + + public async Task SetColumnList(Guid userGuid, IEnumerable columnList, CancellationToken cancellationToken) + { + var reorderedColumns = columnList.Select((column, idx) => new { column, idx }).ToList(); + reorderedColumns.ForEach(reorderedColumn => reorderedColumn.column.Index = reorderedColumn.idx); + columnList = reorderedColumns.Select(reorderedColumn => reorderedColumn.column).ToList(); + var listOfColumns = columnList.GroupBy(cl => cl.Name, (g, d) => d.OrderBy(c => c.Index).First()) + .OrderBy(c => c.Index); + + await _userSettingService.SetDboUserSetting(DboUserSetting.CCI_INITIATIVES_COLUMNS, userGuid, listOfColumns, cancellationToken); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/IInitiativeRollupColumnAliasService.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/IInitiativeRollupColumnAliasService.cs new file mode 100644 index 0000000..dae22bf --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/IInitiativeRollupColumnAliasService.cs @@ -0,0 +1,10 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + public interface IInitiativeRollupColumnAliasService + { + Task UpdateReportingReferences(int rollupColumnId, string name, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/IInitiativeRollupColumnService.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/IInitiativeRollupColumnService.cs new file mode 100644 index 0000000..01c2b37 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/IInitiativeRollupColumnService.cs @@ -0,0 +1,23 @@ +using Strata.ContinuousImprovement.Biz.InitiativeDetailColumns; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + public interface IInitiativeRollupColumnService + { + Task ContainsAsync(int initiativeRollupColumnId, CancellationToken cancellationToken); + Task ContainsNameAsync(string name, CancellationToken cancellationToken, int initiativeRollupColumnId = 0); + Task IsNameValid(string name, CancellationToken cancellationToken, int initiativeRollupColumnId = 0); + Task CreateAsync(InitiativeRollupColumnCreateInfo createInfo, CancellationToken cancellationToken); + Task DeleteAsync(int initiativeRollupColumnId, CancellationToken cancellationToken); + Task> GetAllAsync(CancellationToken cancellationToken); + Task GetByIdAsync(int initiativeRollupColumnId, CancellationToken cancellationToken); + Task> GetByRollupValuesById(int initiativeRollupColumnId, CancellationToken cancellationToken); + Task AddRollupValue(int initiativeRollupColumnId, string value, CancellationToken cancellationToken); + Task UpdateAsync(int initiativeRollupColumnId, InitiativeRollupColumnSaveInfo initiativeRollupColumnSaveInfo, CancellationToken cancellationToken); + Task AddRollupToManagedColumns(InitiativeRollupColumn initiativeRollupColumn, CancellationToken cancellationToken); + InitiativeDetailColumn ConvertRollupColumnToManagedColumn(InitiativeRollupColumn initiativeRollupColumn, List managedColumns); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumn.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumn.cs new file mode 100644 index 0000000..96517a6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumn.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + [Table("InitiativeRollupColumn", Schema = "cci")] + public class InitiativeRollupColumn + { + [Key] + public int InitiativeRollupColumnId { get; set; } + public string Name { get; set; } + public ICollection InitiativeRollupValues { get; set; } = new List(); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnAliasService.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnAliasService.cs new file mode 100644 index 0000000..06b5407 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnAliasService.cs @@ -0,0 +1,110 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Replication; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.DataSchema.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + public class InitiativeRollupColumnAliasService : IInitiativeRollupColumnAliasService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IDataSchemaService _dataSchemaService; + private readonly ILogger _logger; + private readonly IDatabaseService _databaseService; + + public InitiativeRollupColumnAliasService(IAsyncDbContextFactory dbContextFactory + , IDataSchemaService dataSchemaService + , ILogger logger + , IDatabaseService databaseService + ) + { + _dbContextFactory = dbContextFactory; + _dataSchemaService = dataSchemaService; + _logger = logger; + _databaseService = databaseService; + } + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task UpdateReportingReferences(int rollupColumnId, string name, CancellationToken cancellationToken) + { + try + { + await UpdateRESQLReportColumnAlias(rollupColumnId, name, cancellationToken); + await UpdateIARDataSchema(rollupColumnId, name, cancellationToken); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + throw; + } + } + + private async Task UpdateRESQLReportColumnAlias(int rollupColumnId, string name, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var attributes = await dbContext.ScoreAttributes + .Where(a => a.SqlColumnName == $"InitiativeRollup{rollupColumnId}") + .ToListAsync(cancellationToken); + if (!attributes.Any()) { return; } + attributes.ForEach(attribute => attribute.FriendlyName = name); + dbContext.ScoreAttributes.UpdateRange(attributes); + var registeredAttributes = await dbContext.RESQLReportRegisteredAttributes + .Where(a => attributes.Select(attr => attr.AttributeGuid).Contains(a.AttributeGUID)) + .ToListAsync(cancellationToken); + registeredAttributes.ForEach(ra => ra.ColumnAlias = name); + dbContext.RESQLReportRegisteredAttributes.UpdateRange(registeredAttributes); + await dbContext.SaveChangesAsync(cancellationToken); + } + + private async Task UpdateIARDataSchema(int rollupColumnId, string name, CancellationToken cancellationToken) + { + try + { + bool isOnboarded = await _databaseService.IsClientOnboardedAsync(cancellationToken); + + if (_dataSchemaService == null || !isOnboarded) return; + + foreach (var tableName in new[] { "dimopportunity", "diminitiative" }) + { + var iarDimensions = await _dataSchemaService.GetDataTablesBySqlTableNameAsync("fw", tableName, DataSchema.Models.Schema.DataTableType.Dimension, cancellationToken); + var dataTable = iarDimensions.FirstOrDefault(); + + if (dataTable != null) + { + var column = dataTable + .Columns.Find(col => col.SqlObjectName.Equals($"InitiativeRollup{rollupColumnId}", StringComparison.CurrentCultureIgnoreCase)); + if (column != null) + { + column.Name = name; + var saveInfo = dataTable.MapToSaveInfo(); + try + { + await _dataSchemaService.UpdateDataTableAsync(dataTable.DataTableId, saveInfo, cancellationToken); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, tableName); + } + } + } + } + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "UpdateIARDataSchema failed."); + } + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnCreateInfo.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnCreateInfo.cs new file mode 100644 index 0000000..2692617 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnCreateInfo.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + public class InitiativeRollupColumnCreateInfo + { + public string Name { get; set; } + public List Values { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnException.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnException.cs new file mode 100644 index 0000000..b4aa844 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnException.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.Serialization; + +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + [Serializable] + public class InitiativeRollupColumnException : Exception + { + public InitiativeRollupColumnException() + { + } + + public InitiativeRollupColumnException(string message) : base(message) + { + } + + public InitiativeRollupColumnException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InitiativeRollupColumnException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnSaveInfo.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnSaveInfo.cs new file mode 100644 index 0000000..85e6f9b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnSaveInfo.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + public class InitiativeRollupColumnSaveInfo + { + public string Name { get; set; } + public List Updates { get; set; } = new List(); + public List Deletes { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnService.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnService.cs new file mode 100644 index 0000000..4207a14 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupColumnService.cs @@ -0,0 +1,378 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using MoreLinq.Extensions; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.InitiativeDetailColumns; +using Strata.ContinuousImprovement.Biz.UserSetting; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + + public class InitiativeRollupColumnService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IInitiativeDetailColumnService initiativeDetailColumnService, + IInitiativeRollupColumnAliasService aliasService, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IUserSettingService userSettingService, + ILogger logger) : IInitiativeRollupColumnService + { + private readonly ClaimsPrincipal _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + private readonly List _allowedRollupColumnIds = new List { 1, 2, 3 }; + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task ContainsAsync(int initiativeRollupColumnId, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + return await dbContext.InitiativeRollupColumns + .Where(x => _allowedRollupColumnIds.Contains(initiativeRollupColumnId) && x.InitiativeRollupColumnId == initiativeRollupColumnId) + .AnyAsync(cancellationToken); + } + + public async Task ContainsNameAsync(string name, CancellationToken cancellationToken, int initiativeRollupColumnId = 0) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + return await dbContext.InitiativeRollupColumns.AnyAsync(x => x.Name.ToLower() == name.ToLower() && x.InitiativeRollupColumnId != initiativeRollupColumnId, cancellationToken); + } + + public async Task> GetAllAsync(CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var columns = await dbContext.InitiativeRollupColumns + .Include(c => c.InitiativeRollupValues) + .Where(c => _allowedRollupColumnIds.Contains(c.InitiativeRollupColumnId)).ToListAsync(cancellationToken); + // Insert blank (' ') zero values into values for clean UI purposes + columns.ForEach(c => + { + if (!c.InitiativeRollupValues.Any(v => v.InitiativeRollupValueId == 0)) + { + c.InitiativeRollupValues.Add(new InitiativeRollupValue + { + InitiativeRollupColumnId = c.InitiativeRollupColumnId, + InitiativeRollupValueId = 0, + Value = " " + }); + c.InitiativeRollupValues = c.InitiativeRollupValues.OrderBy(v => v.InitiativeRollupValueId).ToList(); + } + }); + return columns; + } + + public async Task GetByIdAsync(int initiativeRollupColumnId, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + return await dbContext.InitiativeRollupColumns + .Include(irc => irc.InitiativeRollupValues) + .AsNoTracking() + .SingleOrDefaultAsync(irc => irc.InitiativeRollupColumnId == initiativeRollupColumnId, cancellationToken); + } + + public async Task CreateAsync(InitiativeRollupColumnCreateInfo createInfo, CancellationToken cancellationToken) + { + try + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (!await IsNameValid(createInfo.Name, cancellationToken)) + { + throw new InitiativeRollupColumnException("Rollup column name already exists."); + } + + if(createInfo.Name.Length > 100) + { + throw new InitiativeRollupColumnException("Rollup column exceeds maximum number of characters"); + } + + var initiativeRollupColumn = new InitiativeRollupColumn + { + Name = createInfo.Name, + InitiativeRollupColumnId = GetNextAvailableRollupColumnId(dbContext), + InitiativeRollupValues = createInfo.Values?.Select(value => + { + var rollupValue = new InitiativeRollupValue() + { + Value = value + }; + + return rollupValue; + }).ToList() + }; + + await dbContext.InitiativeRollupColumns.AddAsync(initiativeRollupColumn, cancellationToken); + await aliasService.UpdateReportingReferences(initiativeRollupColumn.InitiativeRollupColumnId, initiativeRollupColumn.Name, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + await AddRollupToManagedColumns(initiativeRollupColumn, cancellationToken); + return initiativeRollupColumn; + } + catch (Exception ex) + { + logger.LogError(ex, "Failed creating InitiativeRollupColumn"); + throw; + } + } + + public async Task IsNameValid(string name, CancellationToken cancellationToken, int initiativeRollupColumnId = 0) + { + return !await ContainsNameAsync(name, cancellationToken, initiativeRollupColumnId); + } + + private int GetNextAvailableRollupColumnId(JazzDbContext dbContext) + { + var currentRollupColumnIds = dbContext.InitiativeRollupColumns.Select(x => x.InitiativeRollupColumnId); + if (currentRollupColumnIds.Any()) + { + var nextAvailableRollupColumnId = _allowedRollupColumnIds.Except(currentRollupColumnIds).Take(1); + if (!nextAvailableRollupColumnId.Any()) + { + throw new InitiativeRollupColumnException($"Cannot exceed maximum number of rollup columns {_allowedRollupColumnIds.Count()} allowed."); + } + return nextAvailableRollupColumnId.FirstOrDefault(); + } + + return 1; + } + + public async Task UpdateAsync(int initiativeRollupColumnId, InitiativeRollupColumnSaveInfo saveInfo, CancellationToken cancellationToken) + { + if (!await IsNameValid(saveInfo.Name, cancellationToken, initiativeRollupColumnId)) + { + throw new Exception("Rollup column name already exists."); + } + + if (saveInfo.Name.Length > 100) + { + throw new InitiativeRollupColumnException("Rollup column exceeds maximum number of characters"); + } + + var initiativeRollupColumn = await GetByIdAsync(initiativeRollupColumnId, cancellationToken); + if (initiativeRollupColumn == null) return null; + + await using var dbContext = GetDbContext(cancellationToken).Result; + initiativeRollupColumn.Name = saveInfo.Name; + dbContext.InitiativeRollupColumns.Update(initiativeRollupColumn); + await aliasService.UpdateReportingReferences(initiativeRollupColumnId, saveInfo.Name, cancellationToken); + + await UpdateRollupManagedColumn(initiativeRollupColumn, cancellationToken); + + await UpdateInitiativeRollupValuesAsync(dbContext, initiativeRollupColumn, saveInfo, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + + return initiativeRollupColumn; + } + + private async Task UpdateInitiativeRollupValuesAsync(JazzDbContext dbContext, InitiativeRollupColumn initiativeRollupColumn, InitiativeRollupColumnSaveInfo saveInfo, CancellationToken cancellationToken) + { + var deletes = initiativeRollupColumn.InitiativeRollupValues + .Where(v => _allowedRollupColumnIds.Contains(v.InitiativeRollupColumnId) + && v.InitiativeRollupColumnId == initiativeRollupColumn.InitiativeRollupColumnId + && saveInfo.Deletes.Any(d => d.InitiativeRollupValueId == v.InitiativeRollupValueId)); + dbContext.InitiativeRollupValues.RemoveRange(deletes); + await dbContext.SaveChangesAsync(cancellationToken); + foreach (var delete in saveInfo.Deletes) + { + await ClearInitiativeRollups(dbContext, initiativeRollupColumn.InitiativeRollupColumnId, delete.InitiativeRollupValueId); + await ClearStrategicOpportunityRollups(initiativeRollupColumn.InitiativeRollupColumnId, cancellationToken, delete.InitiativeRollupValueId); + } + + foreach (var update in saveInfo.Updates) + { + var existing = initiativeRollupColumn.InitiativeRollupValues.FirstOrDefault(p => p.InitiativeRollupValueId == update.InitiativeRollupValueId); + if (existing == null) + { + var newRollupValue = new InitiativeRollupValue() + { + InitiativeRollupColumnId = initiativeRollupColumn.InitiativeRollupColumnId, + Value = update.Value + }; + + await dbContext.InitiativeRollupValues.AddAsync(newRollupValue, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + } + else + { + existing.Value = update.Value; + dbContext.InitiativeRollupValues.Update(existing); + } + } + } + + public async Task DeleteAsync(int initiativeRollupColumnId, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + await ClearInitiativeRollups(dbContext, initiativeRollupColumnId); + await ClearStrategicOpportunityRollups(initiativeRollupColumnId, cancellationToken); + var initiativeRollupColumn = await GetByIdAsync(initiativeRollupColumnId, cancellationToken); + await userSettingService.DeleteInitiativeColumnForAllUsers(initiativeRollupColumn.Name, cancellationToken); + await userSettingService.UpdateFilterForDeletedGroupCategory($"RollupColumn{initiativeRollupColumnId}", cancellationToken); + + // Cascading delete will remove InitiativeRollupValues associated with this InitiativeRollupColumn + dbContext.InitiativeRollupColumns.Remove(initiativeRollupColumn); + await dbContext.SaveChangesAsync(cancellationToken); + + await RemoveRollupManagedColumn(initiativeRollupColumn, cancellationToken); + } + + private async Task ClearInitiativeRollups(JazzDbContext dbContext, int initiativeRollupColumnId, int? initiativeRollupValueId = null) + { + if (!_allowedRollupColumnIds.Contains(initiativeRollupColumnId)) { return; } + var initiatives = await dbContext.Initiatives + .Where(i => (initiativeRollupColumnId == 1 && initiativeRollupValueId.HasValue ? i.InitiativeRollup1Id == initiativeRollupValueId : i.InitiativeRollup1Id != 0) + || (initiativeRollupColumnId == 2 && initiativeRollupValueId.HasValue ? i.InitiativeRollup2Id == initiativeRollupValueId : i.InitiativeRollup2Id != 0) + || (initiativeRollupColumnId == 3 && initiativeRollupValueId.HasValue ? i.InitiativeRollup3Id == initiativeRollupValueId : i.InitiativeRollup3Id != 0) + ).ToListAsync(); + foreach (var item in initiatives) + { + switch (initiativeRollupColumnId) + { + case 1: + item.InitiativeRollup1Id = 0; + break; + case 2: + item.InitiativeRollup2Id = 0; + break; + case 3: + item.InitiativeRollup3Id = 0; + break; + } + } + dbContext.UpdateRange(initiatives); + } + + private async Task ClearStrategicOpportunityRollups(int initiativeRollupColumnId, CancellationToken cancellationToken, int? initiativeRollupValueId = null) + { + if (!_allowedRollupColumnIds.Contains(initiativeRollupColumnId)) { return; } + var strategicOpportunities = await centralDbContext.StrategicOpportunities + .Where(i => i.StrataId == _user.GetStrataId() && ((initiativeRollupColumnId == 1 && initiativeRollupValueId.HasValue ? i.Rollup1Id == initiativeRollupValueId : i.Rollup1Id != 0) + || (initiativeRollupColumnId == 2 && initiativeRollupValueId.HasValue ? i.Rollup2Id == initiativeRollupValueId : i.Rollup2Id != 0) + || (initiativeRollupColumnId == 3 && initiativeRollupValueId.HasValue ? i.Rollup3Id == initiativeRollupValueId : i.Rollup3Id != 0)) + ).ToListAsync(cancellationToken); + foreach (var item in strategicOpportunities) + { + switch (initiativeRollupColumnId) + { + case 1: + item.Rollup1Id = 0; + break; + case 2: + item.Rollup2Id = 0; + break; + case 3: + item.Rollup3Id = 0; + break; + } + } + centralDbContext.UpdateRange(strategicOpportunities); + await centralDbContext.SaveChangesAsync(cancellationToken); + } + + public async Task> GetByRollupValuesById(int initiativeRollupColumnId, CancellationToken cancellationToken) + { + if (!_allowedRollupColumnIds.Contains(initiativeRollupColumnId)) + { + return null; + } + + await using var dbContext = GetDbContext(cancellationToken).Result; + var rollupValues = await dbContext.InitiativeRollupValues + .Where(x => x.InitiativeRollupColumnId == initiativeRollupColumnId) + .ToListAsync(cancellationToken); + return rollupValues; + } + + public async Task AddRollupValue(int initiativeRollupColumnId, string value, CancellationToken cancellationToken) + { + if (!_allowedRollupColumnIds.Contains(initiativeRollupColumnId)) + { + return null; + } + + await using var dbContext = GetDbContext(cancellationToken).Result; + var rollupValue = (await dbContext.InitiativeRollupValues + .AddAsync(new InitiativeRollupValue + { + InitiativeRollupColumnId = initiativeRollupColumnId, + Value = value + }, cancellationToken)).Entity; + await dbContext.SaveChangesAsync(cancellationToken); + return rollupValue; + } + + public async Task AddRollupToManagedColumns(InitiativeRollupColumn initiativeRollupColumn, CancellationToken cancellationToken) + { + var userGuid = _user?.GetUserGuid() ?? Guid.Empty; + var managedColumns = (await initiativeDetailColumnService.GetColumnList(userGuid, cancellationToken)).ToList(); + var initiativeDetailColumn = ConvertRollupColumnToManagedColumn(initiativeRollupColumn, managedColumns); + var index = managedColumns.FindIndex(col => col.Name == initiativeDetailColumn.Name); + + if (index != -1) + { + managedColumns[index] = initiativeDetailColumn; + } + else + { + managedColumns.Add(initiativeDetailColumn); + } + + await initiativeDetailColumnService.SetColumnList(userGuid, managedColumns, cancellationToken); + await userSettingService.AddInitiativeColumnForAllUsers(initiativeDetailColumn, cancellationToken); + } + + public InitiativeDetailColumn ConvertRollupColumnToManagedColumn(InitiativeRollupColumn initiativeRollupColumn, List managedColumns) + { + var initiativeColumName = initiativeRollupColumn.InitiativeRollupColumnId switch + { + 1 => "InitiativeRollup1Id", + 2 => "InitiativeRollup2Id", + 3 => "InitiativeRollup3Id", + _ => throw new NotImplementedException() + }; + + var initiativeDetailColumn = new InitiativeDetailColumn() + { + Name = initiativeColumName, + Heading = initiativeRollupColumn.Name, + Index = managedColumns.Max(x => x.Index + 1), + InitiativeRollupColumnId = initiativeRollupColumn.InitiativeRollupColumnId + }; + return initiativeDetailColumn; + } + + public async Task UpdateRollupManagedColumn(InitiativeRollupColumn initiativeRollupColumn, CancellationToken cancellationToken) + { + var userGuid = _user?.GetUserGuid() ?? Guid.Empty; + var managedColumns = await initiativeDetailColumnService.GetColumnList(userGuid, cancellationToken); + + var update = managedColumns.FirstOrDefault(x => x.InitiativeRollupColumnId == initiativeRollupColumn.InitiativeRollupColumnId); + if (update != null) + { + await userSettingService.UpdateInitiativeColumnForAllUsers(update.Heading, initiativeRollupColumn.Name, cancellationToken); + await initiativeDetailColumnService.SetColumnList(userGuid, managedColumns, cancellationToken); + } + else + { + await AddRollupToManagedColumns(initiativeRollupColumn, cancellationToken); + } + } + + public async Task RemoveRollupManagedColumn(InitiativeRollupColumn initiativeRollupColumn, CancellationToken cancellationToken) + { + var userGuid = _user?.GetUserGuid() ?? Guid.Empty; + var managedColumns = await initiativeDetailColumnService.GetColumnList(userGuid, cancellationToken); + + managedColumns = managedColumns.Where(x => x.InitiativeRollupColumnId != initiativeRollupColumn.InitiativeRollupColumnId); + await initiativeDetailColumnService.SetColumnList(userGuid, managedColumns, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupValue.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupValue.cs new file mode 100644 index 0000000..67d0569 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupValue.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + [Table("InitiativeRollupValue", Schema = "cci")] + public class InitiativeRollupValue + { + [Key] + public int InitiativeRollupValueId { get; set; } + public int InitiativeRollupColumnId { get; set; } + [JsonIgnore] + public InitiativeRollupColumn InitiativeRollupColumn { get; set; } + public string Value { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupValueSaveInfo.cs b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupValueSaveInfo.cs new file mode 100644 index 0000000..ffa0c68 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/InitiativeRollupColumns/InitiativeRollupValueSaveInfo.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.InitiativeRollupColumns +{ + public class InitiativeRollupValueSaveInfo + { + public int InitiativeRollupValueId { get; set; } + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/Filters/FilterSettings.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/Filters/FilterSettings.cs new file mode 100644 index 0000000..03a177c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/Filters/FilterSettings.cs @@ -0,0 +1,31 @@ +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using System; + +namespace Strata.ContinuousImprovement.Biz.Initiatives.Filters +{ + public class FilterSettings + { + public int Status { get; set; } + public string Name { get; set; } + public Guid[] CostLeaders { get; set; } + public Guid[] Owners { get; set; } + public Guid[] ExecutiveSponsors { get; set; } + public string[] Workstreams { get; set; } + public InitiativeRollupValue[] RollupColumn1Values { get; set; } + public InitiativeRollupValue[] RollupColumn2Values { get; set; } + public InitiativeRollupValue[] RollupColumn3Values { get; set; } + + public FilterSettings() + { + Status = -1; + Name = ""; + CostLeaders = Array.Empty(); + Owners = Array.Empty(); + ExecutiveSponsors = Array.Empty(); + Workstreams = Array.Empty(); + RollupColumn1Values = Array.Empty(); + RollupColumn1Values = Array.Empty(); + RollupColumn1Values = Array.Empty(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/Filters/InitiativeFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/Filters/InitiativeFilterOptions.cs new file mode 100644 index 0000000..0690ccc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/Filters/InitiativeFilterOptions.cs @@ -0,0 +1,20 @@ +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.UserProfiles; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Initiatives.Filters +{ + public class InitiativeFilterOptions + { + public IEnumerable CostDrivers { get; set; } + public IEnumerable CostLeaders { get; set; } + public IEnumerable Owners { get; set; } + public IEnumerable ExecutiveSponsors { get; set; } + public IEnumerable Workstreams { get; set; } + public IEnumerable RollupColumn1Values { get; set; } + public IEnumerable RollupColumn2Values { get; set; } + public IEnumerable RollupColumn3Values { get; set; } + } + +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/Filters/InitiativeFilters.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/Filters/InitiativeFilters.cs new file mode 100644 index 0000000..3f37fea --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/Filters/InitiativeFilters.cs @@ -0,0 +1,92 @@ +using Strata.ContinuousImprovement.Biz.DbContexts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Initiatives.Filters +{ + public class InitiativeFilters + { + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public int Status { get; set; } + public IEnumerable CostDrivers { get; set; } + public IEnumerable CostLeaders { get; set; } + public IEnumerable Owners { get; set; } + public IEnumerable ExecutiveSponsors { get; set; } + public IEnumerable Workstreams { get; set; } + public IEnumerable RollupColumn1Values { get; set; } + public IEnumerable RollupColumn2Values { get; set; } + public IEnumerable RollupColumn3Values { get; set; } + public InitiativeGroupByCategory GroupByCategory { get; set; } + + public InitiativeFilters() + { + ConfigurationGuid = Guid.Empty; + Status = -1; + CostDrivers = new List(); + CostLeaders = new List(); + Owners = new List(); + ExecutiveSponsors = new List(); + Workstreams = new List(); + } + + public IEnumerable>> GetFilters(JazzDbContext context, IEnumerable hiddenColumns) + { + var filters = new List>>(); + + filters.Add(m => m.ConfigurationGUID.Equals(ConfigurationGuid)); + if (!string.IsNullOrEmpty(Name)) + { + var lowerName = Name.ToLower(); + var costDriverIsHidden = hiddenColumns.Contains("CostDriver"); + var workstreamIsHidden = hiddenColumns.Contains("Workstream"); + var costLeaderIsHidden = hiddenColumns.Contains("CostLeader"); + var ownerIsHidden = hiddenColumns.Contains("Owner"); + var executiveSponsorIsHidden = hiddenColumns.Contains("ExecutiveSponsor"); + var rollupColumn1IsHidden = hiddenColumns.Contains(nameof(Initiative.InitiativeRollup1Id)); + var rollupColumn2IsHidden = hiddenColumns.Contains(nameof(Initiative.InitiativeRollup2Id)); + var rollupColumn3IsHidden = hiddenColumns.Contains(nameof(Initiative.InitiativeRollup3Id)); + filters.Add(m => m.Name.Contains(lowerName) + || !costDriverIsHidden && m.CostDriver.Contains(lowerName) + || !workstreamIsHidden && m.Workstream.Contains(lowerName) + || m.CostLeaderUser != null && !costLeaderIsHidden && m.CostLeaderUser.NameFull.Contains(lowerName) + || m.OwnerUser != null && !ownerIsHidden && m.OwnerUser.NameFull.Contains(lowerName) + || m.ExecutiveSponsorUser != null && !executiveSponsorIsHidden && m.ExecutiveSponsorUser.NameFull.Contains(lowerName) + || m.RollupColumn1 != null && !rollupColumn1IsHidden && m.RollupColumn1.Value.Contains(lowerName) + || m.RollupColumn2 != null && !rollupColumn2IsHidden && m.RollupColumn2.Value.Contains(lowerName) + || m.RollupColumn3 != null && !rollupColumn3IsHidden && m.RollupColumn3.Value.Contains(lowerName)); + } + if (Status != -1) + { + if (Status == 0) + { + filters.Add(m => m.CurrentStep == (int)InitiativeSteps.Draft); + } + else + { + filters.Add(m => m.CurrentStep == (int)InitiativeSteps.Tracking); + } + } + if (CostDrivers.Any()) + filters.Add(m => CostDrivers.Contains(m.CostDriver)); + if (CostLeaders.Any()) + filters.Add(m => CostLeaders.Contains(m.CostLeaderGUID)); + if (Owners.Any()) + filters.Add(m => Owners.Contains(m.OwnerGUID)); + if (ExecutiveSponsors.Any()) + filters.Add(m => ExecutiveSponsors.Contains(m.ExecutiveSponsorGUID)); + if (Workstreams.Any()) + filters.Add(m => Workstreams.Contains(m.Workstream)); + if (RollupColumn1Values.Any()) + filters.Add(m => RollupColumn1Values.Contains(m.InitiativeRollup1Id)); + if (RollupColumn2Values.Any()) + filters.Add(m => RollupColumn2Values.Contains(m.InitiativeRollup2Id)); + if (RollupColumn3Values.Any()) + filters.Add(m => RollupColumn3Values.Contains(m.InitiativeRollup3Id)); + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/IInitiativeCreateService.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/IInitiativeCreateService.cs new file mode 100644 index 0000000..e8c5b0c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/IInitiativeCreateService.cs @@ -0,0 +1,15 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public interface IInitiativeCreateService + { + Task GetOpportunity(Guid opportunityGuid, CancellationToken cancellationToken); + Task SaveOpportunity(Guid opportunityGuid, Guid initiativeGuid, CancellationToken cancellationToken); + Task CreateInitiative(Guid dbGuid, Initiative initiative, Guid opportunityGuid, CancellationToken cancellationToken); + Task UpdateInitiative(Initiative initiative, Guid opportunityGuid, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/IInitiativeSavingsService.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/IInitiativeSavingsService.cs new file mode 100644 index 0000000..62df5ab --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/IInitiativeSavingsService.cs @@ -0,0 +1,12 @@ +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.DbContexts; +using System; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public interface IInitiativeSavingsService + { + void ConfigureRampUps(JazzDbContext dbContext, Initiative initiative, Guid configurationGuid); + void ApplySavingsToInitiative(CCOpportunityDataModel opportunity, Initiative initiative, double identifiedSavings); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/IInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/IInitiativeService.cs new file mode 100644 index 0000000..09270b7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/IInitiativeService.cs @@ -0,0 +1,34 @@ +using ClosedXML.Excel; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.Initiatives.Filters; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public interface IInitiativeService + { + Task ContainsAsync(Guid initiativeGuid, CancellationToken cancellationToken); + Task GetById(Guid initiativeGuid, CancellationToken cancellationToken); + Task DeleteAsync(Guid initiativeGuid, CancellationToken cancellationToken); + Task GetInitiativeInfoAsync(Guid initiativeGuid, CancellationToken cancellationToken); + Task> GetInitiatives(InitiativeFilters filters, CancellationToken cancellationToken); + Task> GetPagedAsync(PagingOptions pagingOptions, InitiativeFilters filters, CancellationToken cancellationToken); + Task ExportAsync(bool exportAll, Guid userGuid, SortOptions sortOptions, InitiativeFilters filters, CancellationToken cancellationToken); + Task> GetWorkstreams(CancellationToken cancellationToken); + Task GetFilterSettings(Guid userGuid, CancellationToken cancellationToken); + Task SetFilterSettings(Guid userGuid, InitiativeFilters filters, CancellationToken cancellationToken); + Task GetFilterOptions(Guid configurationGuid, CancellationToken cancellationToken); + Task SetRampupInfo(InitiativeDto initiativeDto, CancellationToken cancellationToken); + Task SetRollupValue(Guid initiativeGuid, int rollupId, int rollupValue, CancellationToken cancellationToken); + Task> GetCriteria(CancellationToken cancellationToken); + Task GetScores(Guid initiativeGuid, CancellationToken cancellationToken); + Task SetScores(InitiativeScoresDto scores, CancellationToken cancellationToken); + Task FinalizeInitiative(Guid initiativeGuid, CancellationToken cancellationToken); + Task RunTracking(Guid initiativeGuid, CancellationToken cancellationToken); + Task FindInitiativeByPartialName(Guid configuration, string name, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/Initiative.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/Initiative.cs new file mode 100644 index 0000000..57260e3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/Initiative.cs @@ -0,0 +1,211 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using Strata.ContinuousImprovement.Biz.UserProfiles; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + + [Table("Initiative", Schema = "cci")] + public class Initiative : IWithSourceOpportunity, IInitiative + { + [Key] + public Guid InitiativeGUID { get; set; } + public string Name { get; set; } + public double SavingsEstimate { get; set; } + public double SavingsCommitted { get; set; } + public double SavingsRampUp01 { get; set; } = 100d; + public double SavingsRampUp02 { get; set; } = 100d; + public double SavingsRampUp03 { get; set; } = 100d; + public double SavingsRampUp04 { get; set; } = 100d; + public double SavingsRampUp05 { get; set; } = 100d; + public double SavingsRampUp06 { get; set; } = 100d; + public double SavingsRampUp07 { get; set; } = 100d; + public double SavingsRampUp08 { get; set; } = 100d; + public double SavingsRampUp09 { get; set; } = 100d; + public double SavingsRampUp10 { get; set; } = 100d; + public double SavingsRampUp11 { get; set; } = 100d; + public double SavingsRampUp12 { get; set; } = 100d; + public Guid OwnerGUID { get; set; } + public Guid ExecutiveSponsorGUID { get; set; } + public Guid AuthorGUID { get; set; } + public Guid CostLeaderGUID { get; set; } + public string CostDriver { get; set; } = ""; + public int CurrentStep { get; set; } = 0; + [NotMapped] + public InitiativeSteps CurrentStepEnum + { + get { return (InitiativeSteps)CurrentStep; } + set { CurrentStep = (int)value; } + } + public string Description { get; set; } = ""; + public short Score01 { get; set; } + public short Score02 { get; set; } + public short Score03 { get; set; } + public short Score04 { get; set; } + public short Score05 { get; set; } + public short Score06 { get; set; } + public short Score07 { get; set; } + public short Score08 { get; set; } + public short Score09 { get; set; } + public short Score10 { get; set; } + public double COI01 { get; set; } + public double COI02 { get; set; } + public double COI03 { get; set; } + public double COI04 { get; set; } + public double COI05 { get; set; } + public double COI06 { get; set; } + public double COI07 { get; set; } + public double COI08 { get; set; } + public double COI09 { get; set; } + public double COI10 { get; set; } + public double COI11 { get; set; } + public double COI12 { get; set; } + public double COICurrentYear { get; set; } + public string GoalStatement { get; set; } = ""; + public string Stakeholders { get; set; } = ""; + public string MilestonesConfigJSON { get; set; } = ""; + public DateTime EffectiveDate { get; set; } + public Guid LastWorkflowHistoryGUID { get; set; } + public double AnnualGoal { get; set; } + public double AnnualProjectActual { get; set; } + public double YTDGoal { get; set; } + public double YTDActual { get; set; } + public double CurrentMonthGoal { get; set; } + public double CurrentMonthActual { get; set; } + public double Month01Goal { get; set; } + public double Month01Actual { get; set; } + public double Month02Goal { get; set; } + public double Month02Actual { get; set; } + public double Month03Goal { get; set; } + public double Month03Actual { get; set; } + public double Month04Goal { get; set; } + public double Month04Actual { get; set; } + public double Month05Goal { get; set; } + public double Month05Actual { get; set; } + public double Month06Goal { get; set; } + public double Month06Actual { get; set; } + public double Month07Goal { get; set; } + public double Month07Actual { get; set; } + public double Month08Goal { get; set; } + public double Month08Actual { get; set; } + public double Month09Goal { get; set; } + public double Month09Actual { get; set; } + public double Month10Goal { get; set; } + public double Month10Actual { get; set; } + public double Month11Goal { get; set; } + public double Month11Actual { get; set; } + public double Month12Goal { get; set; } + public double Month12Actual { get; set; } + public Guid ConfigurationGUID { get; set; } + public string StakeholdersJSON { get; set; } = "[]"; + public string TeamMembers { get; set; } = ""; + public string Workstream { get; set; } = ""; + + [ForeignKey(nameof(ConfigurationGUID))] + public Configuration.Configuration Configuration { get; set; } + + [ForeignKey(nameof(OwnerGUID))] + public UserProfile OwnerUser { get; set; } + + [ForeignKey(nameof(AuthorGUID))] + public UserProfile AuthorUser { get; set; } + + [ForeignKey(nameof(CostLeaderGUID))] + public UserProfile CostLeaderUser { get; set; } + + [ForeignKey(nameof(ExecutiveSponsorGUID))] + public UserProfile ExecutiveSponsorUser { get; set; } + + public virtual List InitiativeComments { get; set; } + + public string Owner => OwnerUser?.NameFull; + public string Author => AuthorUser?.NameFull; + public string CostLeader => CostLeaderUser?.NameFull; + public string ExecutiveSponsor => ExecutiveSponsorUser?.NameFull; + + public int InitiativeRollup1Id { get; set; } + public int InitiativeRollup2Id { get; set; } + public int InitiativeRollup3Id { get; set; } + + [ForeignKey(nameof(InitiativeRollup1Id))] + public InitiativeRollupValue RollupColumn1 { get; set; } + + [ForeignKey(nameof(InitiativeRollup2Id))] + public InitiativeRollupValue RollupColumn2 { get; set; } + + [ForeignKey(nameof(InitiativeRollup3Id))] + public InitiativeRollupValue RollupColumn3 { get; set; } + + public string InitiativeRollup1 => RollupColumn1?.Value; + public string InitiativeRollup2 => RollupColumn2?.Value; + public string InitiativeRollup3 => RollupColumn3?.Value; + + [NotMapped] + public int OpportunityType { get; set; } + [NotMapped] + public long OpportunityId { get; set; } + [NotMapped] + public string OpportunityKey { get; set; } + [NotMapped] + public string Comments { get; set; } + + [NotMapped] + public string SourceOpportunity { get; set; } + [NotMapped] + public long SourceOpportunityId { get; set; } + + public Initiative() + { + InitiativeGUID = Guid.NewGuid(); + SourceOpportunity = ""; + SourceOpportunityId = -1; + } + + public void SetCostLeader(Guid costLeaderGuid) + { + CostLeaderGUID = costLeaderGuid; + } + + public void SetOwner(Guid ownerGuid) + { + OwnerGUID = ownerGuid; + } + + public void SetExecutiveSponsor(Guid execGuid) + { + ExecutiveSponsorGUID = execGuid; + } + + public void SetOpportunityType(int opportunityType) + { + OpportunityType = opportunityType; + } + + internal void Update(Initiative initiative) + { + Name = initiative.Name; + EffectiveDate = initiative.EffectiveDate; + GoalStatement = initiative.GoalStatement; + Workstream = initiative.Workstream; + Description = initiative.Description; + CostLeaderGUID = initiative.CostLeaderGUID; + OwnerGUID = initiative.OwnerGUID; + ExecutiveSponsorGUID = initiative.ExecutiveSponsorGUID; + StakeholdersJSON = initiative.StakeholdersJSON; + TeamMembers = initiative.TeamMembers; + MilestonesConfigJSON = initiative.MilestonesConfigJSON; + InitiativeRollup1Id = initiative.InitiativeRollup1Id; + InitiativeRollup2Id = initiative.InitiativeRollup2Id; + InitiativeRollup3Id = initiative.InitiativeRollup3Id; + } + } + + public interface IInitiative + { + Guid InitiativeGUID { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeComment.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeComment.cs new file mode 100644 index 0000000..1d182f8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeComment.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + [Table("InitiativeComment", Schema = "cci")] + public class InitiativeComment + { + [Key] + public Guid CommentGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public string UserName { get; set; } + public string Comment { get; set; } + public DateTime CreatedAtUtc { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCreateService.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCreateService.cs new file mode 100644 index 0000000..fc57879 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCreateService.cs @@ -0,0 +1,104 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public class InitiativeCreateService : IInitiativeCreateService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IInitiativeSavingsService _initiativeSavingsService; + private readonly IAuthorizationServiceClient _authorizationServiceClient; + private readonly IUpdateFactOpportunitySavingsService _updateFactOpportunitySavingsService; + protected readonly ILogger _logger; + + public InitiativeCreateService( + IAsyncDbContextFactory dbContextFactory, + IInitiativeSavingsService initiativeSavingsService, + IAuthorizationServiceClient authorizationServiceClient, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger logger) + { + _dbContextFactory = dbContextFactory; + _initiativeSavingsService = initiativeSavingsService; + _authorizationServiceClient = authorizationServiceClient; + _updateFactOpportunitySavingsService = updateFactOpportunitySavingsService; + _logger = logger; + } + + public virtual Task GetOpportunity(Guid opportunityGuid, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public virtual Task SaveOpportunity(Guid opportunityGuid, Guid initiativeGuid, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + protected async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task CreateInitiative(Guid dbGuid, Initiative initiative, Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunity = await GetOpportunity(opportunityGuid, cancellationToken); + if (opportunity == null) { return Guid.Empty; } + + try + { + _initiativeSavingsService.ConfigureRampUps(dbContext, initiative, opportunity.ConfigurationGuid); + + initiative.ConfigurationGUID = opportunity.ConfigurationGuid; + await dbContext.Initiatives.AddAsync(initiative, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(initiative.InitiativeGUID, SecurableEntityType.Initiative, cancellationToken); + await _authorizationServiceClient.AddPermissionAsync(initiative.InitiativeGUID, initiative.OwnerGUID, true, false, false, cancellationToken); + + await SaveOpportunity(opportunityGuid, initiative.InitiativeGUID, cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + return initiative.InitiativeGUID; + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, opportunityGuid, initiative.InitiativeGUID); + return Guid.Empty; + } + } + + public async Task UpdateInitiative(Initiative initiative, Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + Initiative updatedItem = null; + var opportunity = await GetOpportunity(opportunityGuid, cancellationToken); + if (opportunity == null) { return null; } + + try + { + var updateInitiative = await dbContext.Initiatives.SingleOrDefaultAsync(x => x.InitiativeGUID == initiative.InitiativeGUID); + if (updateInitiative == null) { return null; } + updateInitiative.Update(initiative); + + await dbContext.SaveChangesAsync(cancellationToken); + + await SaveOpportunity(opportunityGuid, initiative.InitiativeGUID, cancellationToken); + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, opportunityGuid, initiative.InitiativeGUID); + } + + return updatedItem; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCriteria.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCriteria.cs new file mode 100644 index 0000000..1715f8c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCriteria.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + [Table("InitiativeCriteria", Schema = "cci")] + public class InitiativeCriteria + { + [Key] + public Guid InitiativeCriteriaGUID { get; set; } + public string Name { get; set; } + public string Type { get; set; } + public int ScoringMethod { get; set; } + public string InitiativeDataField { get; set; } + public string Config { get; set; } // json blob + public byte SortOrder { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCriteriaDto.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCriteriaDto.cs new file mode 100644 index 0000000..624cbb6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCriteriaDto.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public class InitiativeCriteriaDto + { + public Guid InitiativeCriteriaGUID { get; set; } + public string Name { get; set; } + public string InitiativeDataField { get; set; } + public IList Options { get; set; } + } + + public class InitiativeCriteriaOption + { + public int Value { get; set; } + public string Text { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCriteriaQueryResult.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCriteriaQueryResult.cs new file mode 100644 index 0000000..a733116 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeCriteriaQueryResult.cs @@ -0,0 +1,13 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public class InitiativeCriteriaQueryResult + { + public Guid InitiativeCriteriaGUID { get; set; } + public string Name { get; set; } + public string InitiativeDataField { get; set; } + public string DisplayText { get; set; } + public int Score { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDataModelMapper.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDataModelMapper.cs new file mode 100644 index 0000000..0d8bb6c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDataModelMapper.cs @@ -0,0 +1,10 @@ +using Riok.Mapperly.Abstractions; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class InitiativeDataModelMapper + { + public static partial InitiativeDto Map(this Initiative src); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDetail.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDetail.cs new file mode 100644 index 0000000..d14852b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDetail.cs @@ -0,0 +1,51 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public class InitiativeDetail : InitiativeExport + { + public Guid InitiativeGuid { get; set; } + public int OpportunityType { get; set; } + } + + public class InitiativeExport : IWithSourceOpportunity + { + public string Name { get; set; } + public string GoalStatement { get; set; } + public string Workstream { get; set; } + public string PlannedImprovements { get; set; } + public string CostLeader { get; set; } + public string Owner { get; set; } + public string ExecutiveSponsor { get; set; } + public string Stakeholders { get; set; } + public string TeamMembers { get; set; } + public string Milestones { get; set; } + public string CostDriver { get; set; } + public InitiativeSteps Status { get; set; } + public string InitiativeStatus { get; set; } + public string CurrentStep { get; set; } + public DateTime TrackingStartMonth { get; set; } + public DateTime CurrentMonth { get; set; } + public double CurrentMonthGoal { get; set; } + public double CurrentMonthActual { get; set; } + public double CurrentMonthVariance => CurrentMonthActual - CurrentMonthGoal; + public double YTDGoal { get; set; } + public double YTDActual { get; set; } + public double YTDVariance => YTDActual - YTDGoal; + public double SavingsCommitted { get; set; } + public double AnnualGoal { get; set; } + public string SourceOpportunity { get; set; } + public long SourceOpportunityId { get; set; } + public string OpportunityKey { get; set; } + public long OpportunityId { get; set; } + public string Comments { get; set; } + public string InitiativeRollup1 { get; set; } + public string InitiativeRollup2 { get; set; } + public string InitiativeRollup3 { get; set; } + public int InitiativeRollup1Id { get; set; } + public int InitiativeRollup2Id { get; set; } + public int InitiativeRollup3Id { get; set; } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDetailTreeGridModel.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDetailTreeGridModel.cs new file mode 100644 index 0000000..ebc2fe4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDetailTreeGridModel.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public class InitiativeDetailTreeGridModel + { + public string Key { get; set; } + public InitiativeDetail Data { get; set; } + public IEnumerable Children { get; set; } + + public InitiativeDetailTreeGridModel(IGrouping initiativeGrouping) + { + Key = initiativeGrouping.Key; + Data = null; + Children = initiativeGrouping.Select(i => new InitiativeDetailTreeGridModel(i)); + } + + public InitiativeDetailTreeGridModel(InitiativeDetail data) + { + Key = data.InitiativeGuid.ToString(); + Data = data; + Children = null; + } + } + + public enum InitiativeGroupByCategory + { + CostDriver, + CostLeader, + ExecutiveSponsor, + Owner, + Status, + Workstream, + RollupColumn1, + RollupColumn2, + RollupColumn3, + None + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDto.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDto.cs new file mode 100644 index 0000000..7975de4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDto.cs @@ -0,0 +1,82 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public class InitiativeDto + { + public string Name { get; set; } + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public string CostDriver { get; set; } + public string TrackingStart { get; set; } + public string StartMonth { get; set; } = ""; + public string EndMonth { get; set; } = ""; + public string GoalStatement { get; set; } + public string Workstream { get; set; } + public string PlannedImprovements { get; set; } = ""; + public bool IsFinalized { get; set; } = false; + public bool IsLocked { get; set; } = false; + public Guid CostLeader { get; set; } = Guid.Empty; + public Guid Owner { get; set; } = Guid.Empty; + public Guid ExecutiveSponsor { get; set; } = Guid.Empty; + public string[] Stakeholders { get; set; } + public string TeamMembers { get; set; } + public string Milestone1 { get; set; } + public string Milestone2 { get; set; } + public string Milestone3 { get; set; } + public string Milestone4 { get; set; } + public string Milestone5 { get; set; } + public string Milestone6 { get; set; } + public string Milestone7 { get; set; } + public string Milestone8 { get; set; } + public string Milestone9 { get; set; } + public string Milestone10 { get; set; } + public string DueDate1 { get; set; } = ""; + public string DueDate2 { get; set; } = ""; + public string DueDate3 { get; set; } = ""; + public string DueDate4 { get; set; } = ""; + public string DueDate5 { get; set; } = ""; + public string DueDate6 { get; set; } = ""; + public string DueDate7 { get; set; } = ""; + public string DueDate8 { get; set; } = ""; + public string DueDate9 { get; set; } = ""; + public string DueDate10 { get; set; } = ""; + public double SavingsRampUp01 { get; set; } = 100d; + public double SavingsRampUp02 { get; set; } = 100d; + public double SavingsRampUp03 { get; set; } = 100d; + public double SavingsRampUp04 { get; set; } = 100d; + public double SavingsRampUp05 { get; set; } = 100d; + public double SavingsRampUp06 { get; set; } = 100d; + public double SavingsRampUp07 { get; set; } = 100d; + public double SavingsRampUp08 { get; set; } = 100d; + public double SavingsRampUp09 { get; set; } = 100d; + public double SavingsRampUp10 { get; set; } = 100d; + public double SavingsRampUp11 { get; set; } = 100d; + public double SavingsRampUp12 { get; set; } = 100d; + public double COI01 { get; set; } = 0; + public double COI02 { get; set; } = 0; + public double COI03 { get; set; } = 0; + public double COI04 { get; set; } = 0; + public double COI05 { get; set; } = 0; + public double COI06 { get; set; } = 0; + public double COI07 { get; set; } = 0; + public double COI08 { get; set; } = 0; + public double COI09 { get; set; } = 0; + public double COI10 { get; set; } = 0; + public double COI11 { get; set; } = 0; + public double COI12 { get; set; } = 0; + public byte Score01 { get; set; } + public byte Score02 { get; set; } + public byte Score03 { get; set; } + public byte Score04 { get; set; } + public byte Score05 { get; set; } + public byte Score06 { get; set; } + public byte Score07 { get; set; } + public byte Score08 { get; set; } + public byte Score09 { get; set; } + public byte Score10 { get; set; } + public int InitiativeRollup1Id { get; set; } + public int InitiativeRollup2Id { get; set; } + public int InitiativeRollup3Id { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDtoExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDtoExtensions.cs new file mode 100644 index 0000000..215dbf9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeDtoExtensions.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.Initiatives; +using System; +using System.Globalization; + +namespace Strata.ContinuousImprovement.Api.Controllers +{ + public static class InitiativeDtoExtensions + { + public static string Json(this InitiativeDto source, MilestoneConfiguration milestone) + { + milestone.Milestones[0].Name = source.Milestone1; + milestone.Milestones[0].DueDate = source.DueDate1.JustTheDate(); + milestone.Milestones[1].Name = source.Milestone2; + milestone.Milestones[1].DueDate = source.DueDate2.JustTheDate(); + milestone.Milestones[2].Name = source.Milestone3; + milestone.Milestones[2].DueDate = source.DueDate3.JustTheDate(); + milestone.Milestones[3].Name = source.Milestone4; + milestone.Milestones[3].DueDate = source.DueDate4.JustTheDate(); + milestone.Milestones[4].Name = source.Milestone5; + milestone.Milestones[4].DueDate = source.DueDate5.JustTheDate(); + milestone.Milestones[5].Name = source.Milestone6; + milestone.Milestones[5].DueDate = source.DueDate6.JustTheDate(); + milestone.Milestones[6].Name = source.Milestone7; + milestone.Milestones[6].DueDate = source.DueDate8.JustTheDate(); + milestone.Milestones[7].Name = source.Milestone8; + milestone.Milestones[7].DueDate = source.DueDate8.JustTheDate(); + milestone.Milestones[8].Name = source.Milestone9; + milestone.Milestones[8].DueDate = source.DueDate9.JustTheDate(); + milestone.Milestones[9].Name = source.Milestone10; + milestone.Milestones[9].DueDate = source.DueDate10.JustTheDate(); + return JsonConvert.SerializeObject(milestone); + } + + public static string JustTheDate(this string dateSource) + { + if (string.IsNullOrEmpty(dateSource)) { return dateSource; } + if (dateSource.Equals("Invalid Date", StringComparison.CurrentCultureIgnoreCase)) { return ""; } + return DateTime.ParseExact(dateSource, "yyyyMMdd", CultureInfo.InvariantCulture).ToString("MM/dd/yyyy"); + } + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeInfo.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeInfo.cs new file mode 100644 index 0000000..18b89e7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeInfo.cs @@ -0,0 +1,110 @@ +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public class InitiativeInfo : InitiativeDto + { + public IList TrackingMonths { get; set; } + } + + public static class InitiativeInfoExtensions + { + /// + /// Converts Initiative to InitiativeInfo for react UI + /// + /// + /// + /// + /// + /// TODO: This should also be checking the user to see if they are part of the Admin group + /// Strata.Web.Core.Shared.State.Global.ActiveUserGroupMapping.IsCostLeader() from Jazz + /// _userGroupMapping.IsInGroup(Strata.CS.Base.GlobalID.UserGroup.CCICostLeader); + /// + /// + public static InitiativeInfo InitiativeInfo(this Initiative initiative, Guid opportunityGuid, IList trackingMonths, DateTime startDate, DateTime endDate) + { + var milestones = JsonConvert.DeserializeObject(initiative.MilestonesConfigJSON); + var info = new InitiativeInfo + { + OpportunityGuid = opportunityGuid, + InitiativeGuid = initiative.InitiativeGUID, + Name = initiative.Name, + PlannedImprovements = initiative.Description, + GoalStatement = initiative.GoalStatement, + CostDriver = initiative.CostDriver, + Owner = initiative.OwnerGUID, + CostLeader = initiative.CostLeaderGUID, + ExecutiveSponsor = initiative.ExecutiveSponsorGUID, + TeamMembers = initiative.TeamMembers, + Workstream = initiative.Workstream, + TrackingMonths = trackingMonths, + TrackingStart = initiative.EffectiveDate.ToString("yyyyMMdd"), + IsLocked = initiative.CurrentStepEnum != InitiativeSteps.Draft, + StartMonth = startDate.ToString("yyyy-MM-dd"), + EndMonth = endDate.ToString("yyyy-MM-dd"), + Stakeholders = JsonConvert.DeserializeObject>(initiative.StakeholdersJSON) + .Select(x => x.UserGUID.ToString()).ToArray(), + Milestone1 = milestones.Milestones[0].Name, + Milestone2 = milestones.Milestones[1].Name, + Milestone3 = milestones.Milestones[2].Name, + Milestone4 = milestones.Milestones[3].Name, + Milestone5 = milestones.Milestones[4].Name, + Milestone6 = milestones.Milestones[5].Name, + Milestone7 = milestones.Milestones[6].Name, + Milestone8 = milestones.Milestones[7].Name, + Milestone9 = milestones.Milestones[8].Name, + Milestone10 = milestones.Milestones[9].Name, + DueDate1 = milestones.Milestones[0].DueDate.ToMoment(), + DueDate2 = milestones.Milestones[1].DueDate.ToMoment(), + DueDate3 = milestones.Milestones[2].DueDate.ToMoment(), + DueDate4 = milestones.Milestones[3].DueDate.ToMoment(), + DueDate5 = milestones.Milestones[4].DueDate.ToMoment(), + DueDate6 = milestones.Milestones[5].DueDate.ToMoment(), + DueDate7 = milestones.Milestones[6].DueDate.ToMoment(), + DueDate8 = milestones.Milestones[7].DueDate.ToMoment(), + DueDate9 = milestones.Milestones[8].DueDate.ToMoment(), + DueDate10 = milestones.Milestones[9].DueDate.ToMoment(), + SavingsRampUp01 = initiative.SavingsRampUp01, + SavingsRampUp02 = initiative.SavingsRampUp02, + SavingsRampUp03 = initiative.SavingsRampUp03, + SavingsRampUp04 = initiative.SavingsRampUp04, + SavingsRampUp05 = initiative.SavingsRampUp05, + SavingsRampUp06 = initiative.SavingsRampUp06, + SavingsRampUp07 = initiative.SavingsRampUp07, + SavingsRampUp08 = initiative.SavingsRampUp08, + SavingsRampUp09 = initiative.SavingsRampUp09, + SavingsRampUp10 = initiative.SavingsRampUp10, + SavingsRampUp11 = initiative.SavingsRampUp11, + SavingsRampUp12 = initiative.SavingsRampUp12, + COI01 = initiative.COI01, + COI02 = initiative.COI02, + COI03 = initiative.COI03, + COI04 = initiative.COI04, + COI05 = initiative.COI05, + COI06 = initiative.COI06, + COI07 = initiative.COI07, + COI08 = initiative.COI08, + COI09 = initiative.COI09, + COI10 = initiative.COI10, + COI11 = initiative.COI11, + COI12 = initiative.COI12, + InitiativeRollup1Id = initiative.InitiativeRollup1Id, + InitiativeRollup2Id = initiative.InitiativeRollup2Id, + InitiativeRollup3Id = initiative.InitiativeRollup3Id + }; + + return info; + } + } + + public class MilestoneJSON + { + public MileStone[] Milestones { get; set; } + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeInfoMapper.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeInfoMapper.cs new file mode 100644 index 0000000..4b46068 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeInfoMapper.cs @@ -0,0 +1,42 @@ +using Newtonsoft.Json; +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.Utilities; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class InitiativeInfoMapper + { + + public static InitiativeInfo MapToInfo(this Initiative src) + { + var milestones = JsonConvert.DeserializeObject(src.MilestonesConfigJSON); + var mapped = src.MapForInfo(); + mapped.TrackingStart = src.EffectiveDate.ToString("yyyyMMdd"); + mapped.IsLocked = src.CurrentStepEnum != InitiativeSteps.Draft; + mapped.Milestone1 = milestones.Milestones[0].Name; + mapped.Milestone2 = milestones.Milestones[1].Name; + mapped.Milestone3 = milestones.Milestones[2].Name; + mapped.Milestone4 = milestones.Milestones[3].Name; + mapped.Milestone5 = milestones.Milestones[4].Name; + mapped.Milestone6 = milestones.Milestones[5].Name; + mapped.Milestone7 = milestones.Milestones[6].Name; + mapped.Milestone8 = milestones.Milestones[7].Name; + mapped.Milestone9 = milestones.Milestones[8].Name; + mapped.Milestone10 = milestones.Milestones[9].Name; + mapped.DueDate1 = milestones.Milestones[0].DueDate.ToMoment(); + mapped.DueDate2 = milestones.Milestones[1].DueDate.ToMoment(); + mapped.DueDate3 = milestones.Milestones[2].DueDate.ToMoment(); + mapped.DueDate4 = milestones.Milestones[3].DueDate.ToMoment(); + mapped.DueDate5 = milestones.Milestones[4].DueDate.ToMoment(); + mapped.DueDate6 = milestones.Milestones[5].DueDate.ToMoment(); + mapped.DueDate7 = milestones.Milestones[6].DueDate.ToMoment(); + mapped.DueDate8 = milestones.Milestones[7].DueDate.ToMoment(); + mapped.DueDate9 = milestones.Milestones[8].DueDate.ToMoment(); + mapped.DueDate10 = milestones.Milestones[9].DueDate.ToMoment(); + return mapped; + } + + private static partial InitiativeInfo MapForInfo(this Initiative src); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeMapper.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeMapper.cs new file mode 100644 index 0000000..1574969 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeMapper.cs @@ -0,0 +1,74 @@ +using Newtonsoft.Json; +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Api.Controllers; +using Strata.ContinuousImprovement.Biz.FreeForm.Initiatives; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + [Mapper(UseDeepCloning = true)] + public static partial class InitiativeMapper + { + + public static Initiative MapToInitiative(this InitiativeDto src, IStakeholderUser stakeholderUser) + { + var mapped = src.Mapped(); + if (mapped == null) return default; + if (mapped.InitiativeGUID == Guid.Empty) + { + mapped.InitiativeGUID = Guid.NewGuid(); + } + mapped.EffectiveDate = src.TrackingStart.ToInitiativeEffectiveDate(); + mapped.MilestonesConfigJSON = src.Json((new MilestoneConfiguration())); + mapped.StakeholdersJSON = stakeholderUser.Resolve(src.Stakeholders); + return mapped; + } + + public static InitiativeExport MapToExport(this Initiative src) + { + var mapped = src.Mapped(); + if (mapped == null) return default; + var opportunityType = (OpportunityTypes)src.OpportunityType; + mapped.CurrentMonth = src.Configuration?.GetTrackingCurrentMonthStartDate(opportunityType) ?? DateTime.MinValue; + mapped.InitiativeStatus = src.CurrentStepEnum == InitiativeSteps.Tracking ? "Final" : src.CurrentStepEnum.ToString(); + mapped.CurrentStep = src.CurrentStepEnum.ToString(); + mapped.Stakeholders = string.Join("|", JsonConvert.DeserializeObject>(src.StakeholdersJSON) + .Select(sh => sh.FullName)); + mapped.Milestones = string.Join("|", JsonConvert.DeserializeObject(src.MilestonesConfigJSON) + .Milestones.Where(m => !string.IsNullOrEmpty(m.Name)) + .Select(m => $"{m.DueDate:d} {m.Name}")); + + return mapped; + } + + [MapProperty(nameof(Initiative.CurrentStepEnum), nameof(InitiativeDetail.Status))] + [MapProperty(nameof(Initiative.EffectiveDate), nameof(InitiativeDetail.TrackingStartMonth))] + [MapProperty(nameof(Initiative.InitiativeGUID), nameof(InitiativeDetail.InitiativeGuid))] + public static partial InitiativeDetail MapToDetail(this Initiative src); + + [MapperIgnoreTarget(nameof(Initiative.Author))] + [MapperIgnoreTarget(nameof(Initiative.Owner))] + [MapperIgnoreTarget(nameof(Initiative.CostLeader))] + [MapperIgnoreTarget(nameof(Initiative.ExecutiveSponsor))] + [MapperIgnoreTarget(nameof(Initiative.Stakeholders))] + [MapProperty(nameof(InitiativeDto.InitiativeGuid), nameof(Initiative.InitiativeGUID))] + [MapProperty(nameof(InitiativeDto.PlannedImprovements), nameof(Initiative.Description))] + [MapProperty(nameof(InitiativeDto.Owner), nameof(Initiative.AuthorGUID))] + [MapProperty(nameof(InitiativeDto.Owner), nameof(Initiative.OwnerGUID))] + [MapProperty(nameof(InitiativeDto.CostLeader), nameof(Initiative.CostLeaderGUID))] + [MapProperty(nameof(InitiativeDto.ExecutiveSponsor), nameof(Initiative.ExecutiveSponsorGUID))] + private static partial Initiative Mapped(this InitiativeDto src); + + [MapProperty(nameof(Initiative.CurrentStepEnum), nameof(InitiativeExport.Status))] + [MapProperty(nameof(Initiative.EffectiveDate), nameof(InitiativeExport.TrackingStartMonth))] + [MapProperty(nameof(Initiative.Description), nameof(InitiativeExport.PlannedImprovements))] + private static partial InitiativeExport Mapped(this Initiative src); + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeSavingsService.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeSavingsService.cs new file mode 100644 index 0000000..9c9c231 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeSavingsService.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public class InitiativeSavingsService : IInitiativeSavingsService + { + private readonly IFiscalMonthResolver _fiscalMonthResolver; + private readonly ILogger _logger; + + public InitiativeSavingsService(IFiscalMonthResolver fiscalMonthResolver, + ILogger logger) + { + _fiscalMonthResolver = fiscalMonthResolver; + _logger = logger; + } + + public void ConfigureRampUps(JazzDbContext dbContext, Initiative initiative, Guid configurationGuid) + { + var configuration = dbContext.Configurations.SingleOrDefault(c => c.ConfigurationGuid == configurationGuid); + if (configuration == null) { return; } + for (var i = 1; i <= 12; i++) + { + var fiscalMonth = _fiscalMonthResolver.GetByFiscalMonthId(i); + var trackingMonths = initiative.EffectiveDate.GetMonthsBetweenDates(configuration.OrgDefinedEndDate.AddYears(1)); + if (!trackingMonths.Contains(fiscalMonth.FiscalMonthId)) + { + initiative.TrySetPropertyValue($"SavingsRampUp{i:00}", 0.0); + } + } + } + + public void ApplySavingsToInitiative(CCOpportunityDataModel opportunity, Initiative initiative, double identifiedSavings) + { + if (opportunity.LevelCases == null + || !opportunity.LevelCases.Any() + || !opportunity.LevelCases.Where(c => c.IsBaseline).Any()) { return; } + var annualGoal = 0.0; + var baselineLevelCases = opportunity.LevelCases + .Where(c => c.IsBaseline) + .GroupBy(c => c.Month, (month, data) => new { Month = month, Cases = data.Sum(x => x.Cases) }) + .ToDictionary(k => k.Month, v => v.Cases); + var totalCases = 0d; + if (baselineLevelCases != null && baselineLevelCases.Any()) + { + totalCases = baselineLevelCases.Sum(c => c.Value); + } + var annualGoalSpread = identifiedSavings / 12; + + for (var i = 1; i <= 12; i++) + { + var fiscalMonth = _fiscalMonthResolver.GetByFiscalMonthId(i); + double monthlyRampUp = 0.0; + // Spread annual goal evenly across months if not Seasonal + double monthlySavings = !opportunity.IsSeasonal ? annualGoalSpread : 0.0; + + if (opportunity.IsSeasonal && baselineLevelCases.ContainsKey(i)) + { + // Spread annual goal over monthly-weighted case count + var monthlyWeight = baselineLevelCases[i].DivideBy(totalCases); + monthlySavings = identifiedSavings * monthlyWeight; + } + + monthlyRampUp = initiative.TryGetPropertyValue($"SavingsRampUp{i:00}", out double rampUp) ? (double)rampUp / 100.0 : 1.0; + var monthlyGoal = monthlySavings * monthlyRampUp; + initiative.TrySetPropertyValue($"{fiscalMonth.MonthColumnName}Goal", monthlyGoal); + annualGoal += monthlyGoal; + } + + initiative.AnnualGoal = annualGoal; + initiative.SavingsCommitted = identifiedSavings; + initiative.SavingsEstimate = identifiedSavings; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeScore.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeScore.cs new file mode 100644 index 0000000..6965a4c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeScore.cs @@ -0,0 +1,37 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + [Table("InitiativeScore", Schema = "cci")] + public class InitiativeScore + { + [Key] + public Guid InitiativeScoreGUID { get; set; } + public Guid InitiativeCriteriaGUID { get; set; } + public Guid InitiativeGUID { get; set; } + public short Score { get; set; } + public Guid UserGUID { get; set; } + } + + public class InitiativeScoresDto + { + public Guid InitiativeGUID { get; set; } + public short Score01 { get; set; } + public short Score02 { get; set; } + public short Score03 { get; set; } + public short Score04 { get; set; } + public short Score05 { get; set; } + public short Score07 { get; set; } + public short Score08 { get; set; } + public short Score09 { get; set; } + public short Score10 { get; set; } + } + + public class InitiativeScoreResult + { + public string DataField { get; set; } + public short Score { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeService.cs new file mode 100644 index 0000000..4272913 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeService.cs @@ -0,0 +1,1002 @@ +using ClosedXML.Excel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Newtonsoft.Json; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.InitiativeDetailColumns; +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using Strata.ContinuousImprovement.Biz.Initiatives.Filters; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Replication; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.UserSetting; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.Hangfire.Jazz.Client; +using Strata.Hangfire.Jazz.Client.Models; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Security.Claims; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using static Strata.ContinuousImprovement.Biz.Export.ExportUtils; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + + public class InitiativeService : IInitiativeService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly CentralDbContext _centralDbContext; + private readonly ClaimsPrincipal _user; + private readonly IUserSettingService _userSettingService; + private readonly IJazzHangfireServiceClient _hangfireServiceClient; + private readonly IConfigurationService _configurationService; + private readonly IFiscalMonthResolver _fiscalMonthResolver; + private readonly IInitiativeDetailColumnService _initiativeDetailColumnService; + private readonly IInitiativeRollupColumnService _initiativeRollupColumnService; + private readonly IDatabaseService _databaseService; + private readonly IUpdateFactOpportunitySavingsService _updateFactOpportunitySavingsService; + private readonly ILogger _logger; + +#pragma warning disable S107 // Methods should not have too many parameters + public InitiativeService( + IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IUserSettingService userSettingService, + IJazzHangfireServiceClient hangfireServiceClient, + IConfigurationService configurationService, + IFiscalMonthResolver fiscalMonthResolver, + IInitiativeDetailColumnService initiativeDetailColumnService, + IInitiativeRollupColumnService initiativeRollupColumnService, + IDatabaseService databaseService, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger logger) +#pragma warning restore S107 // Methods should not have too many parameters + { + _dbContextFactory = dbContextFactory; + _centralDbContext = centralDbContext; + _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + _userSettingService = userSettingService; + _hangfireServiceClient = hangfireServiceClient; + _configurationService = configurationService; + _fiscalMonthResolver = fiscalMonthResolver; + _initiativeDetailColumnService = initiativeDetailColumnService; + _initiativeRollupColumnService = initiativeRollupColumnService; + _databaseService = databaseService; + _updateFactOpportunitySavingsService = updateFactOpportunitySavingsService; + _logger = logger; + } + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task ContainsAsync(Guid initiativeGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return false; + + return await dbContext.Initiatives.ContainsAsync(new Initiative() { InitiativeGUID = initiativeGuid }, cancellationToken); + } + + public async Task GetById(Guid initiativeGuid, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return null; + + var query = dbContext.AddUserFilter(dbContext.Initiatives.AsQueryable(), _user); + return await query.FirstOrDefaultAsync(i => i.InitiativeGUID == initiativeGuid); + } + + public async Task DeleteAsync(Guid initiativeGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return false; + + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(i => i.InitiativeGUID.Equals(initiativeGuid), cancellationToken); + if (initiative == null) return false; + + dbContext.Initiatives.Remove(initiative); + + var result = await dbContext.SaveChangesAsync(cancellationToken); + return result > 0; + } + + public async Task GetInitiativeInfoAsync(Guid initiativeGuid, CancellationToken cancellationToken) + { + if (initiativeGuid == Guid.Empty) { return default; } + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunityItem = dbContext?.AllOpportunities + .SingleOrDefault(vo => vo.InitiativeGuid.Equals(initiativeGuid)); + if (opportunityItem == null) { return default; } + var configuration = await _configurationService.GetConfigurationAsync(opportunityItem.ConfigurationGuid, cancellationToken); + if (configuration != null && configuration.FiscalMonthResolver == null) + { + configuration.FiscalMonthResolver = _fiscalMonthResolver; + } + + if (opportunityItem.InitiativeGuid != Guid.Empty) + { + var query = dbContext.AddUserFilter(dbContext.Initiatives.AsQueryable(), _user); + var initiative = query.SingleOrDefault(i => i.InitiativeGUID == opportunityItem.InitiativeGuid); + var fiscalMonths = configuration.GetTrackingFiscalMonths(OpportunityTypes.OrgDefined, initiative.EffectiveDate); + return initiative?.InitiativeInfo(opportunityItem.OpportunityGuid, fiscalMonths, configuration.OrgDefinedTrackingStartDate, configuration.OrgDefinedTrackingEndDate); + } + var info = new InitiativeInfo + { + OpportunityGuid = opportunityItem.OpportunityGuid, + Name = opportunityItem.Name, + CostDriver = opportunityItem.CostDriver, + StartMonth = configuration.OrgDefinedTrackingStartDate.ToString("yyyy-MM-dd"), + EndMonth = configuration.OrgDefinedTrackingEndDate.ToString("yyyy-MM-dd") + }; + return info; + } + + /// + /// This is here to insure that these two tables rows aren't accidentally deleted + /// These are added to allow for the use of the .Include to resolve the names in the queries + /// + /// + /// + private async Task VerifyDefaultRollupValue(CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + //Make certain that the 0 Rollup Column is available + var defaultRollupColum = await dbContext.InitiativeRollupColumns + .SingleOrDefaultAsync(c => c.InitiativeRollupColumnId == 0, cancellationToken); + if (defaultRollupColum == null) + { + await dbContext.InitiativeRollupColumns.AddAsync(new InitiativeRollupColumn + { + InitiativeRollupColumnId = 0, + Name = "" + }, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + } + + //Make certain that 0 Rollup Value is availble + var defaultRollups = await dbContext.InitiativeRollupValues + .SingleOrDefaultAsync(c => c.InitiativeRollupColumnId == 0 && c.InitiativeRollupValueId == 0, cancellationToken); + if (defaultRollups == null) + { + await dbContext.InitiativeRollupValues.AddAsync(new InitiativeRollupValue + { + InitiativeRollupValueId = 0, + InitiativeRollupColumnId = 0, + Value = "" + }, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + } + } + + public async Task> GetInitiatives(InitiativeFilters filters, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return new List(); + var opportunitiesWithInitiatives = await dbContext.AddUserFilter(dbContext.AllOpportunities + .Where(opp => opp.ConfigurationGuid == filters.ConfigurationGuid + && opp.InitiativeGuid != Guid.Empty), _user) + .ToListAsync(cancellationToken); + var query = await GetInitiativeQuery(dbContext, null, filters, cancellationToken); + var data = await query.ToListAsync(cancellationToken); + + foreach (var initiative in data) + { + var opportunity = opportunitiesWithInitiatives.FirstOrDefault(opp => opp.InitiativeGuid == initiative.InitiativeGUID); + if (opportunity == null) + { + continue; + } + + // set opp type from opportunity + initiative.OpportunityType = opportunity.OpportunityType; + + var clientNetworkOpportunityLink = await _centralDbContext.ClientNetworkOpportunityLinks + .Include(link => link.ClientNetworkOpportunity) + .ThenInclude(cno => cno.NetworkOpportunity) + .FirstOrDefaultAsync(link => link.OpportunityGuid == opportunity.OpportunityGuid, cancellationToken); + if (clientNetworkOpportunityLink == null) continue; + ((IWithSourceOpportunity)initiative).SetSourceOpportunity(clientNetworkOpportunityLink.ClientNetworkOpportunity); + } + + var detailData = data.Select(i => i.MapToDetail()); + switch (filters.GroupByCategory) + { + case InitiativeGroupByCategory.CostDriver: + return detailData.OrderBy(i => i.CostDriver).GroupBy(i => i.CostDriver).Select(group => new InitiativeDetailTreeGridModel(group)); + case InitiativeGroupByCategory.CostLeader: + return detailData.OrderBy(i => i.CostLeader).GroupBy(i => i.CostLeader).Select(group => new InitiativeDetailTreeGridModel(group)); + case InitiativeGroupByCategory.ExecutiveSponsor: + return detailData.OrderBy(i => i.ExecutiveSponsor).GroupBy(i => i.ExecutiveSponsor).Select(group => new InitiativeDetailTreeGridModel(group)); + case InitiativeGroupByCategory.Owner: + return detailData.OrderBy(i => i.Owner).GroupBy(i => i.Owner).Select(group => new InitiativeDetailTreeGridModel(group)); + case InitiativeGroupByCategory.Status: + return detailData.OrderBy(i => i.Status).GroupBy(i => i.Status.ToString()).Select(group => new InitiativeDetailTreeGridModel(group)); + case InitiativeGroupByCategory.Workstream: + return detailData.OrderBy(i => i.Workstream).GroupBy(i => i.Workstream).Select(group => new InitiativeDetailTreeGridModel(group)); + case InitiativeGroupByCategory.RollupColumn1: + return detailData.OrderBy(i => i.InitiativeRollup1).GroupBy(i => i.InitiativeRollup1).Select(group => new InitiativeDetailTreeGridModel(group)); + case InitiativeGroupByCategory.RollupColumn2: + return detailData.OrderBy(i => i.InitiativeRollup2).GroupBy(i => i.InitiativeRollup2).Select(group => new InitiativeDetailTreeGridModel(group)); + case InitiativeGroupByCategory.RollupColumn3: + return detailData.OrderBy(i => i.InitiativeRollup3).GroupBy(i => i.InitiativeRollup3).Select(group => new InitiativeDetailTreeGridModel(group)); + case InitiativeGroupByCategory.None: + return detailData.Select(group => new InitiativeDetailTreeGridModel(group)); + default: + throw new NotImplementedException(); + } + } + + public async Task> GetPagedAsync(PagingOptions pagingOptions, InitiativeFilters filters, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return new PagedApiResponse() { Data = new List(), PagingInfo = new PagingInfo() }; + var opportunitiesWithInitiatives = await dbContext.AllOpportunities + .Where(opp => opp.ConfigurationGuid == filters.ConfigurationGuid + && opp.InitiativeGuid != Guid.Empty) + .ToListAsync(cancellationToken); + + PagedApiResponse pageData = default; + var query = await GetInitiativeQuery(dbContext, pagingOptions, filters, cancellationToken); + pageData = await query.ToPagedAsync(pagingOptions, cancellationToken); + foreach (var initiative in pageData.Data) + { + var opp = opportunitiesWithInitiatives.FirstOrDefault(o => o.InitiativeGuid == initiative.InitiativeGUID); + if (opp == null) { continue; } + initiative.OpportunityType = opp.OpportunityType; + initiative.OpportunityKey = opp.OpportunityKey; + initiative.OpportunityId = opp.OpportunityId; + initiative.Comments = string.Join("|", initiative.InitiativeComments.OrderBy(c => c.CreatedAtUtc).Select(c => $"{c.UserName} {c.CreatedAtUtc:g} {c.Comment}")); + } + + pageData.Data = await UpdateSourceOpportunity(dbContext, filters.ConfigurationGuid, pageData.Data, cancellationToken); + return pageData; + } + + private async Task> GetInitiativeQuery(JazzDbContext dbContext, PagingOptions pagingOptions, InitiativeFilters filters, CancellationToken cancellationToken) + { + await VerifyDefaultRollupValue(cancellationToken); + + var userGuid = _user?.GetUserGuid() ?? Guid.Empty; + var hiddenColumns = new List(); + if (_user != null) + { + hiddenColumns = (await _initiativeDetailColumnService.GetColumnList(userGuid, cancellationToken)).Where(x => x.IsHidden).ToList(); + } + var query = dbContext.AddUserFilter(dbContext.Initiatives.AsQueryable(), _user) + .Include(i => i.OwnerUser) + .Include(i => i.ExecutiveSponsorUser) + .Include(i => i.CostLeaderUser) + .Include(i => i.Configuration) + .Include(i => i.InitiativeComments) + .Include(i => i.RollupColumn1) + .Include(i => i.RollupColumn2) + .Include(i => i.RollupColumn3) + .AsQueryable(); + + if (pagingOptions != null) + { + switch (pagingOptions.SortField) + { + case "initiativerollup1id": + query = pagingOptions.SortOrder == SortOrder.Asc + ? query.OrderBy(i => i.RollupColumn1.Value) + : query.OrderByDescending(i => i.RollupColumn1.Value); + pagingOptions.SortField = ""; + break; + case "initiativerollup2id": + query = pagingOptions.SortOrder == SortOrder.Asc + ? query.OrderBy(i => i.RollupColumn2.Value) + : query.OrderByDescending(i => i.RollupColumn2.Value); + pagingOptions.SortField = ""; + break; + case "initiativerollup3id": + query = pagingOptions.SortOrder == SortOrder.Asc + ? query.OrderBy(i => i.RollupColumn3.Value) + : query.OrderByDescending(i => i.RollupColumn3.Value); + pagingOptions.SortField = ""; + break; + case "owner": + query = pagingOptions.SortOrder == SortOrder.Asc + ? query.OrderBy(i => i.OwnerUser.NameFull) + : query.OrderByDescending(i => i.OwnerUser.NameFull); + pagingOptions.SortField = ""; + break; + case "executiveSponsor": + query = pagingOptions.SortOrder == SortOrder.Asc + ? query.OrderBy(i => i.ExecutiveSponsorUser.NameFull) + : query.OrderByDescending(i => i.ExecutiveSponsorUser.NameFull); + pagingOptions.SortField = ""; + break; + case "costLeader": + query = pagingOptions.SortOrder == SortOrder.Asc + ? query.OrderBy(i => i.CostLeaderUser.NameFull) + : query.OrderByDescending(i => i.CostLeaderUser.NameFull); + pagingOptions.SortField = ""; + break; + case "currentMonthVariance": + query = pagingOptions.SortOrder == SortOrder.Asc + ? query.OrderBy(i => i.CurrentMonthActual - i.CurrentMonthGoal) + : query.OrderByDescending(i => i.CurrentMonthActual - i.CurrentMonthGoal); + pagingOptions.SortField = ""; + break; + case "ytdVariance": + query = pagingOptions.SortOrder == SortOrder.Asc + ? query.OrderBy(i => i.YTDActual - i.YTDGoal) + : query.OrderByDescending(i => i.YTDActual - i.YTDGoal); + pagingOptions.SortField = ""; + break; + case "status": + pagingOptions.SortField = nameof(Initiative.CurrentStep); + break; + case "trackingStartMonth": + pagingOptions.SortField = nameof(Initiative.EffectiveDate); + break; + } + } + + var where = filters.GetFilters(dbContext, hiddenColumns.Select(idc => idc.Name)); + foreach (var e in where) + { + try + { + query = query.Where(e); + } + catch + { + // Intentionally left empty + } + } + + // this is done to filter out initiatives without opportunities + var opportunityTypes = Enum.GetValues().Select(x => (int)x); + query = query.Join(dbContext.AllOpportunities.Where(x => opportunityTypes.Contains(x.OpportunityType)), + init => init.InitiativeGUID, opp => opp.InitiativeGuid, (initiatives, opp) => initiatives); + return query; + } + + private async Task> UpdateSourceOpportunity(JazzDbContext dbContext, Guid configurationGuid, IEnumerable data, CancellationToken cancellationToken) + { + var strataId = _user.GetStrataId(); + var sourceOpportunities = await _centralDbContext.ClientNetworkOpportunities + .Where(cno => cno.StrataId == strataId) + .Include(cno => cno.NetworkOpportunity) + .Include(ol => ol.Opportunities.Where(opp => opp.ConfigurationGuid == configurationGuid)) + .SelectMany(cno => cno.Opportunities.Select(o => new { o.OpportunityGuid, cno.NetworkOpportunity.Name })) + .ToDictionaryAsync(key => key.OpportunityGuid, value => value.Name, cancellationToken); + if (!sourceOpportunities.Any()) { return data; } + + foreach (var initiative in data) + { + var opp = await dbContext.AllOpportunities.SingleOrDefaultAsync(o => o.ConfigurationGuid == configurationGuid + && o.InitiativeGuid == initiative.InitiativeGUID + && sourceOpportunities.Keys.Contains(o.OpportunityGuid), cancellationToken); + if (opp == null) continue; + initiative.SourceOpportunity = sourceOpportunities.TryGetValue(opp.OpportunityGuid, out var name) ? name : ""; + } + return data; + } + +#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high + public async Task ExportAsync(bool exportAll, Guid userGuid, SortOptions sortOptions, InitiativeFilters filters, CancellationToken cancellationToken) +#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high + { + if (exportAll) + { + filters = new InitiativeFilters + { + ConfigurationGuid = filters.ConfigurationGuid, + RollupColumn1Values = Enumerable.Empty(), + RollupColumn2Values = Enumerable.Empty(), + RollupColumn3Values = Enumerable.Empty() + }; + } + var initiatives = await GetPagedAsync( + new PagingOptions + { + SortField = sortOptions.SortField, + SortOrder = sortOptions.SortOrder, + First = 0, + Rows = int.MaxValue + }, filters, cancellationToken); + var data = initiatives.Data.Select(i => i.MapToExport()); + + switch (filters.GroupByCategory) + { + case InitiativeGroupByCategory.CostDriver: + data = data.OrderBy(i => i.CostDriver).GroupBy(i => i.CostDriver).SelectMany(group => group); + break; + case InitiativeGroupByCategory.CostLeader: + data = data.OrderBy(i => i.CostLeader).GroupBy(i => i.CostLeader).SelectMany(group => group); + break; + case InitiativeGroupByCategory.ExecutiveSponsor: + data = data.OrderBy(i => i.ExecutiveSponsor).GroupBy(i => i.ExecutiveSponsor).SelectMany(group => group); + break; + case InitiativeGroupByCategory.Owner: + data = data.OrderBy(i => i.Owner).GroupBy(i => i.Owner).SelectMany(group => group); + break; + case InitiativeGroupByCategory.Status: + data = data.OrderBy(i => i.Status).GroupBy(i => i.Status).SelectMany(group => group); + break; + case InitiativeGroupByCategory.Workstream: + data = data.OrderBy(i => i.Workstream).GroupBy(i => i.Workstream).SelectMany(group => group); + break; + case InitiativeGroupByCategory.RollupColumn1: + data = data.OrderBy(i => i.InitiativeRollup1).GroupBy(i => i.InitiativeRollup1).SelectMany(group => group); + break; + case InitiativeGroupByCategory.RollupColumn2: + data = data.OrderBy(i => i.InitiativeRollup2).GroupBy(i => i.InitiativeRollup2).SelectMany(group => group); + break; + case InitiativeGroupByCategory.RollupColumn3: + data = data.OrderBy(i => i.InitiativeRollup3).GroupBy(i => i.InitiativeRollup3).SelectMany(group => group); + break; + } + + var columnOrder = (await _initiativeDetailColumnService.GetColumnList(_user?.GetUserGuid() ?? Guid.Empty, cancellationToken)).ToList(); + var statusExists = columnOrder.FirstOrDefault(col => col.Name == "Status" && !col.IsHidden); + if (statusExists != null) + { + var idx = columnOrder.IndexOf(statusExists); + columnOrder.Insert(idx, new InitiativeDetailColumn + { + Name = "InitiativeStatus", + IsHidden = false, + IsLocked = false, + Heading = "InitiativeStatus", + Index = statusExists.Index + }); + } + var exportOptions = new ExportOptions { EmptyMessage = "No Initiatives" }; + var wb = CreateExcelWorkbook(data, exportOptions); + if (!data.Any()) { return wb; } + var ws = wb.Worksheets.First(); + var table = ws.Tables.First(); + var header = table.HeadersRow(); + var columnDictionary = data.ColumnDictionary(); + int col = 0; + var rollupColumns = (await _initiativeRollupColumnService.GetAllAsync(cancellationToken)) + .OrderBy(x => x.InitiativeRollupColumnId).ToList(); + + List contents; + columnDictionary.ForEach(kvp => + { + var wsColumn = columnDictionary.ContainsKey(kvp.Key) ? ws.Column(columnDictionary[kvp.Key]) : default; + if (wsColumn == null) return; + if (new[] { "CurrentMonthGoal", "CurrentMonthActual", "CurrentMonthVariance", + "YTDGoal", "YTDActual", "YTDVariance", + "SavingsCommitted", "AnnualGoal" }.Contains(kvp.Key)) + { + contents = ws.Column(kvp.Value).CellsUsed(XLCellsUsedOptions.Contents).Skip(1).ToList(); + contents.ForEach(cell => + cell.SetDataType(XLDataType.Number).Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Right)); + wsColumn.Style.NumberFormat.NumberFormatId = (int)NumberFormatId.PoundCommaPoundPoundZeroDotZeroZeroRedNegative; + } + switch (kvp.Key) + { + case "OpportunityId": + case "OpportunityKey": + case "SourceOpportunityId": + case "InitiativeRollup1Id": + case "InitiativeRollup2Id": + case "InitiativeRollup3Id": + wsColumn.Hide(); + break; + case "InitiativeRollup1": + case "InitiativeRollup2": + case "InitiativeRollup3": + var id = Regex.Replace(kvp.Key, @"[^\d]*([\d])", "$1"); + var rollupColumn = rollupColumns.SingleOrDefault(c => c.InitiativeRollupColumnId == int.Parse(id) && c.Name != ""); + if (rollupColumn != null) + { + col = wsColumn.ColumnNumber(); + header.Cell(col).Value = rollupColumn.Name; + } + else + { + wsColumn.Hide(); + } + break; + case "Status": + wsColumn.Hide(); + col = wsColumn.ColumnNumber(); + header.Cell(col).Value = "CurrentStep"; + if (columnDictionary.ContainsKey("InitiativeStatus")) + { + col = ws.Column(columnDictionary["InitiativeStatus"]).ColumnNumber(); + header.Cell(col).Value = "Status"; + } + break; + case "TrackingStartMonth": + contents = ws.Column(kvp.Value).CellsUsed(XLCellsUsedOptions.Contents).Skip(1).ToList(); + contents.ForEach(cell => + cell.SetDataType(XLDataType.DateTime).Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Left)); + wsColumn.Style.DateFormat.SetFormat("MMMM"); + break; + case "CurrentMonth": + contents = ws.Column(kvp.Value).CellsUsed(XLCellsUsedOptions.Contents).Skip(1).ToList(); + contents.ForEach(cell => + cell.SetDataType(XLDataType.DateTime).Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Left)); + wsColumn.Style.DateFormat.SetFormat("MMM-yyyy"); + break; + case "CurrentMonthGoal": + col = ws.Column(columnDictionary["CurrentMonthGoal"]).ColumnNumber(); + header.Cell(col).Value = "Current Month Committed"; + break; + case "YTDGoal": + col = ws.Column(columnDictionary["YTDGoal"]).ColumnNumber(); + header.Cell(col).Value = "YTD Committed"; + break; + case "SavingsCommitted": + col = ws.Column(columnDictionary["SavingsCommitted"]).ColumnNumber(); + header.Cell(col).Value = "12-Month Goal Savings"; + break; + case "AnnualGoal": + col = ws.Column(columnDictionary["AnnualGoal"]).ColumnNumber(); + header.Cell(col).Value = "Committed FY Savings"; + break; + } + }); + + return wb; + } + + public async Task Create(Initiative initiative, Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var opportunity = dbContext.AllOpportunities.SingleOrDefault(opp => opp.OpportunityGuid == opportunityGuid); + if (opportunity == null) { return Guid.Empty; } + try + { + initiative.ConfigurationGUID = opportunity.ConfigurationGuid; + await dbContext.Initiatives.AddAsync(initiative, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + opportunity.InitiativeGuid = initiative.InitiativeGUID; + await dbContext.SaveChangesAsync(cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + return opportunity.InitiativeGuid; + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, opportunityGuid, initiative.InitiativeGUID); + return Guid.Empty; + } + } + + public async Task> GetWorkstreams(CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + return dbContext.InitiativeWorkstreamsQuery().ToList(); + } + + public async Task GetFilterSettings(Guid userGuid, CancellationToken cancellationToken) + { + var filters = await _userSettingService.GetDboUserSetting(DboUserSetting.CCI_INITIATIVES_FILTERS, userGuid, cancellationToken); + filters.Name = string.Empty; + return filters; + } + + public async Task SetFilterSettings(Guid userGuid, InitiativeFilters filters, CancellationToken cancellationToken) + { + filters.Name = string.Empty; + await _userSettingService.SetDboUserSetting(DboUserSetting.CCI_INITIATIVES_FILTERS, userGuid, filters, cancellationToken); + return filters; + } + + public async Task GetFilterOptions(Guid configurationGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return null; + + var opportunityTypes = Enum.GetValues().Cast(); + var costDrivers = await dbContext.AllOpportunities + .Where(x => opportunityTypes.Contains((byte)x.OpportunityType)) + .Select(opp => opp.CostDriver).Distinct().ToListAsync(cancellationToken); + var initiatives = await dbContext.Initiatives + .Include(i => i.CostLeaderUser) + .Include(i => i.OwnerUser) + .Include(i => i.ExecutiveSponsorUser) + .Where(i => i.ConfigurationGUID == configurationGuid) + .ToListAsync(cancellationToken); + + var workstreams = await dbContext.Initiatives + .Select(i => i.Workstream.Trim()).Distinct() + .Select(i => new NameItem(i)).ToListAsync(cancellationToken); + + return new InitiativeFilterOptions + { + CostDrivers = initiatives.Where(i => costDrivers.Contains(i.CostDriver)).Select(i => i.CostDriver) + .Distinct().OrderBy(x => x).Select(x => new NameItem(x)), + CostLeaders = initiatives.Select(i => i.CostLeaderUser).Distinct().OrderBy(x => x.NameFull), + Owners = initiatives.Select(i => i.OwnerUser).Distinct().OrderBy(x => x.NameFull), + ExecutiveSponsors = initiatives.Select(i => i.ExecutiveSponsorUser).Distinct().OrderBy(x => x.NameFull), + Workstreams = workstreams.OrderBy(x => x.Name) + }; + } + + public async Task SetRampupInfo(InitiativeDto initiativeDto, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(i => i.InitiativeGUID == initiativeDto.InitiativeGuid, cancellationToken); + if (initiative == null) { return; } + + initiative.SavingsRampUp01 = initiativeDto.SavingsRampUp01; + initiative.SavingsRampUp02 = initiativeDto.SavingsRampUp02; + initiative.SavingsRampUp03 = initiativeDto.SavingsRampUp03; + initiative.SavingsRampUp04 = initiativeDto.SavingsRampUp04; + initiative.SavingsRampUp05 = initiativeDto.SavingsRampUp05; + initiative.SavingsRampUp06 = initiativeDto.SavingsRampUp06; + initiative.SavingsRampUp07 = initiativeDto.SavingsRampUp07; + initiative.SavingsRampUp08 = initiativeDto.SavingsRampUp08; + initiative.SavingsRampUp09 = initiativeDto.SavingsRampUp09; + initiative.SavingsRampUp10 = initiativeDto.SavingsRampUp10; + initiative.SavingsRampUp11 = initiativeDto.SavingsRampUp11; + initiative.SavingsRampUp12 = initiativeDto.SavingsRampUp12; + + initiative.COI01 = initiativeDto.COI01; + initiative.COI02 = initiativeDto.COI02; + initiative.COI03 = initiativeDto.COI03; + initiative.COI04 = initiativeDto.COI04; + initiative.COI05 = initiativeDto.COI05; + initiative.COI06 = initiativeDto.COI06; + initiative.COI07 = initiativeDto.COI07; + initiative.COI08 = initiativeDto.COI08; + initiative.COI09 = initiativeDto.COI09; + initiative.COI10 = initiativeDto.COI10; + initiative.COI11 = initiativeDto.COI11; + initiative.COI12 = initiativeDto.COI12; + + dbContext.Update(initiative); + await dbContext.SaveChangesAsync(cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + } + + public async Task SetRollupValue(Guid initiativeGuid, int rollupId, int rollupValue, CancellationToken cancellationToken) + { + await using var dbContext = await GetDbContext(cancellationToken); + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(i => i.InitiativeGUID == initiativeGuid, cancellationToken); + switch (rollupId) + { + case 1: + initiative.InitiativeRollup1Id = rollupValue; + break; + case 2: + initiative.InitiativeRollup2Id = rollupValue; + break; + case 3: + initiative.InitiativeRollup3Id = rollupValue; + break; + } + dbContext.Update(initiative); + await dbContext.SaveChangesAsync(cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + } + + public async Task> GetCriteria(CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return null; + + var query = $@" + SELECT + InitiativeCriteriaGUID, + Name, + InitiativeDataField, + icco.DisplayText, + icco.Score + FROM cci.InitiativeCriteria ic + CROSS APPLY OPENJSON(ic.Config) WITH ( + Options NVARCHAR(MAX) '$.Options' AS JSON + ) icc + CROSS APPLY OPENJSON(icc.Options) WITH ( + DisplayText NVARCHAR(MAX) '$.DisplayText', + Score int '$.Score', + IconCls NVARCHAR(MAX) '$.IconCls' + ) icco + ORDER BY SortOrder, icco.Score + "; + + var results = dbContext.InitiativeCriteriaQueryResults.FromSqlRaw(query).ToList(); + var criteria = results + .GroupBy(i => new + { + i.InitiativeCriteriaGUID, + i.Name, + i.InitiativeDataField + }) + .Select(group => new InitiativeCriteriaDto + { + InitiativeCriteriaGUID = group.Key.InitiativeCriteriaGUID, + Name = group.Key.Name, + InitiativeDataField = group.Key.InitiativeDataField, + Options = group.Select(i => new InitiativeCriteriaOption { Text = i.DisplayText, Value = i.Score }).ToList() + }); + + return criteria.ToList(); + } + + public async Task GetScores(Guid initiativeGuid, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return null; + + var scoreResults = dbContext.InitiativeScore + .Where(i => i.InitiativeGUID == initiativeGuid) + .Join(dbContext.InitiativeCriteria, + s => s.InitiativeCriteriaGUID, + c => c.InitiativeCriteriaGUID, + (s, c) => new InitiativeScoreResult + { + DataField = c.InitiativeDataField, + Score = s.Score + }) + .ToList(); + + var scores = new InitiativeScoresDto(); + foreach (var score in scoreResults) + { + switch (score.DataField) + { + case "Score01": + scores.Score01 = score.Score; + break; + case "Score02": + scores.Score02 = score.Score; + break; + case "Score03": + scores.Score03 = score.Score; + break; + case "Score04": + scores.Score04 = score.Score; + break; + case "Score05": + scores.Score05 = score.Score; + break; + case "Score07": + scores.Score07 = score.Score; + break; + case "Score08": + scores.Score08 = score.Score; + break; + case "Score09": + scores.Score09 = score.Score; + break; + case "Score10": + scores.Score10 = score.Score; + break; + default: + break; + } + } + + return scores; + } + + public async Task SetScores(InitiativeScoresDto scores, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) return false; + + try + { + var dataFieldGuidLookup = await dbContext.InitiativeCriteria + .Select(ic => new { ic.InitiativeDataField, ic.InitiativeCriteriaGUID }) + .ToDictionaryAsync(ic => ic.InitiativeDataField, ic => ic.InitiativeCriteriaGUID, cancellationToken); + + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(i => i.InitiativeGUID == scores.InitiativeGUID, cancellationToken); + initiative.Score01 = scores.Score01; + initiative.Score02 = scores.Score02; + initiative.Score03 = scores.Score03; + initiative.Score04 = scores.Score04; + initiative.Score05 = scores.Score05; + initiative.Score07 = scores.Score07; + initiative.Score08 = scores.Score08; + initiative.Score09 = scores.Score09; + initiative.Score10 = scores.Score10; + dbContext.Update(initiative); + + // update cci.InitiativeScores + var initiativeScores = await dbContext.InitiativeScore.Where(i => i.InitiativeGUID == scores.InitiativeGUID).ToListAsync(cancellationToken); + if (!initiativeScores.Any()) + { + var newScores = new List + { + new InitiativeScore + { + InitiativeCriteriaGUID = dataFieldGuidLookup["Score01"], + InitiativeGUID = scores.InitiativeGUID, + Score = scores.Score01 + }, + new InitiativeScore + { + InitiativeCriteriaGUID = dataFieldGuidLookup["Score02"], + InitiativeGUID = scores.InitiativeGUID, + Score = scores.Score02 + }, + new InitiativeScore + { + InitiativeCriteriaGUID = dataFieldGuidLookup["Score03"], + InitiativeGUID = scores.InitiativeGUID, + Score = scores.Score03 + }, + new InitiativeScore + { + InitiativeCriteriaGUID = dataFieldGuidLookup["Score04"], + InitiativeGUID = scores.InitiativeGUID, + Score = scores.Score04 + }, + new InitiativeScore + { + InitiativeCriteriaGUID = dataFieldGuidLookup["Score05"], + InitiativeGUID = scores.InitiativeGUID, + Score = scores.Score05 + }, + new InitiativeScore + { + InitiativeCriteriaGUID = dataFieldGuidLookup["Score07"], + InitiativeGUID = scores.InitiativeGUID, + Score = scores.Score07 + }, + new InitiativeScore + { + InitiativeCriteriaGUID = dataFieldGuidLookup["Score08"], + InitiativeGUID = scores.InitiativeGUID, + Score = scores.Score08 + }, + new InitiativeScore + { + InitiativeCriteriaGUID = dataFieldGuidLookup["Score09"], + InitiativeGUID = scores.InitiativeGUID, + Score = scores.Score09 + }, + new InitiativeScore + { + InitiativeCriteriaGUID = dataFieldGuidLookup["Score10"], + InitiativeGUID = scores.InitiativeGUID, + Score = scores.Score10 + }, + }; + await dbContext.AddRangeAsync(newScores, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + return true; + } + + var iniativeScores = await dbContext.InitiativeScore.Where(i => i.InitiativeGUID == scores.InitiativeGUID).ToListAsync(cancellationToken); + foreach (var score in iniativeScores) + { + var dataField = dataFieldGuidLookup.FirstOrDefault(x => x.Value == score.InitiativeCriteriaGUID).Key; + switch (dataField) + { + case "Score01": + score.Score = scores.Score01; + break; + case "Score02": + score.Score = scores.Score02; + break; + case "Score03": + score.Score = scores.Score03; + break; + case "Score04": + score.Score = scores.Score04; + break; + case "Score05": + score.Score = scores.Score05; + break; + case "Score07": + score.Score = scores.Score07; + break; + case "Score08": + score.Score = scores.Score08; + break; + case "Score09": + score.Score = scores.Score09; + break; + case "Score10": + score.Score = scores.Score10; + break; + default: + break; + } + } + dbContext.UpdateRange(iniativeScores); + await dbContext.SaveChangesAsync(cancellationToken); + return true; + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, "Criteria Scoring", Guid.Empty, scores.InitiativeGUID); + return false; + } + } + + public async Task FinalizeInitiative(Guid initiativeGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var initiative = await dbContext.Initiatives + .SingleOrDefaultAsync(i => i.InitiativeGUID.Equals(initiativeGuid), cancellationToken) + ?? throw new Exception($"Initiative with ID {initiativeGuid} not found."); + + try + { + initiative.CurrentStep = (int)InitiativeSteps.Tracking; + await dbContext.SaveChangesAsync(cancellationToken); + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, Guid.Empty, initiativeGuid); + } + } + + public async Task RunTracking(Guid initiativeGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var opportunity = await dbContext.AllOpportunities.SingleOrDefaultAsync(opp => opp.InitiativeGuid.Equals(initiativeGuid), cancellationToken); + if (!new[] { (int)OpportunityTypes.FreeForm, (int)OpportunityTypes.ChargeCode, (int)OpportunityTypes.Encounter }.Contains(opportunity.OpportunityType)) + { + return Guid.Empty; + } + var initiative = await dbContext.Initiatives.SingleOrDefaultAsync(i => i.InitiativeGUID.Equals(initiativeGuid), cancellationToken); + + try + { + EnqueueJobDto enqueueJobDto = new EnqueueJobDto + { + TaskName = opportunity.OpportunityType switch + { + (int)OpportunityTypes.ChargeCode => "Strata.CS.Jazz.Biz.CCI.Tasks.Custom.ChargeCode.RunChargeCodeTrackingTask, Strata.CS.Jazz.Biz.CCI", + (int)OpportunityTypes.Encounter => "Strata.CS.Jazz.Biz.CCI.Tasks.Encounter.RunEncounterTrackingTask, Strata.CS.Jazz.Biz.CCI", + (int)OpportunityTypes.FreeForm => "Strata.CS.Jazz.Biz.CCI.Tasks.Custom.FreeForm.RunFreeFormTrackingTask, Strata.CS.Jazz.Biz.CCI", + (int)OpportunityTypes.GeneralLedger => "Strata.CS.Jazz.Biz.CCI.Tasks.Custom.GeneralLedger.RunGeneralLedgerTrackingTask, Strata.CS.Jazz.Biz.CCI", + (int)OpportunityTypes.Payroll => "Strata.CS.Jazz.Biz.CCI.Tasks.Payroll.RunPayrollTrackingTask, Strata.CS.Jazz.Biz.CCI", + _ => throw new ArgumentOutOfRangeException(nameof(opportunity.OpportunityType)) + }, + ContextData = opportunity.OpportunityType == (int)OpportunityTypes.Encounter ? JsonConvert.SerializeObject(new + { + opportunityGuid = opportunity.OpportunityGuid, + configurationGuid = initiative.ConfigurationGUID + }) : JsonConvert.SerializeObject(new + { + configurationGuid = initiative.ConfigurationGUID, + initiativeGuid + }) + }; + var job = enqueueJobDto; + + var response = await _hangfireServiceClient.EnqueueJobAsync(job, cancellationToken); + await _hangfireServiceClient.EnqueueJobAfterAsync(new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.RunUpdateFactOpportunitySavingsTask, Strata.CS.Jazz.Biz.CCI" + }, response.JobId, JobContinuationOptions.OnAnyFinishedState, cancellationToken); + return response.JobId; + } + catch (Exception ex) + { + _logger.LogInitiativeError(ex, dbContext, initiative.Name, Guid.Empty, initiativeGuid); + throw; + } + } + + public async Task FindInitiativeByPartialName(Guid configurationGuid, string name, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + return await dbContext.Initiatives + .FirstOrDefaultAsync(initiative => initiative.ConfigurationGUID == configurationGuid + && initiative.Name.Contains(name), cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeSteps.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeSteps.cs new file mode 100644 index 0000000..bc04e8d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/InitiativeSteps.cs @@ -0,0 +1,13 @@ +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public enum InitiativeSteps + { + Draft, + ExecutiveSponsorReview, + OwnerReview, + CostLeaderReview, + ExecutiveSponsorApproval, + Tracking + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/MileStone.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/MileStone.cs new file mode 100644 index 0000000..e8a3602 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/MileStone.cs @@ -0,0 +1,12 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + [Serializable] + public class MileStone + { + public string Name; + public string DueDate; + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/MilestoneConfiguration.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/MilestoneConfiguration.cs new file mode 100644 index 0000000..07fd3a6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/MilestoneConfiguration.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + [Serializable] + public class MilestoneConfiguration + { + public List Milestones { get; set; } + + public MilestoneConfiguration() + { + Milestones = Enumerable.Range(0, 10).Select(p => new MileStone() { }).ToList(); + } + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/User.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/User.cs new file mode 100644 index 0000000..97f0ceb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/User.cs @@ -0,0 +1,12 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + [Serializable] + public class User + { + public Guid UserGUID; + public string FullName; + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Initiatives/WorkstreamDto.cs b/src/Strata.ContinuousImprovement.Biz/Initiatives/WorkstreamDto.cs new file mode 100644 index 0000000..9b46a40 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Initiatives/WorkstreamDto.cs @@ -0,0 +1,7 @@ +namespace Strata.ContinuousImprovement.Biz.Initiatives +{ + public class WorkstreamDto + { + public string Workstream { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/MappedColumns/IMappedColumn.cs b/src/Strata.ContinuousImprovement.Biz/MappedColumns/IMappedColumn.cs new file mode 100644 index 0000000..1f14a66 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/MappedColumns/IMappedColumn.cs @@ -0,0 +1,11 @@ +namespace Strata.ContinuousImprovement.Biz.MappedColumns +{ + public interface IMappedColumn + { + string Heading { get; set; } + int Index { get; set; } + bool IsHidden { get; set; } + bool IsLocked { get; set; } + string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/MappedColumns/IMappedColumnService.cs b/src/Strata.ContinuousImprovement.Biz/MappedColumns/IMappedColumnService.cs new file mode 100644 index 0000000..9ebcab0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/MappedColumns/IMappedColumnService.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.MappedColumns +{ + public interface IMappedColumnService + { + Task> GetColumnList(CancellationToken cancellationToken) where T : class; + Task> GetColumnList(string name, CancellationToken cancellationToken); + IEnumerable GetDefaultColumnList(params string[] lockedColumns) where T : class; + IEnumerable GetDefaultColumnList(string name, params string[] lockedColumns); + Task> GetManagedColumnList(params string[] managedColumnsToIgnore) where T : class; + Task SetColumnList(IEnumerable columnList, CancellationToken cancellationToken) where T : class; + Task SetColumnList(string name, IEnumerable columnList, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/MappedColumns/MappedColumn.cs b/src/Strata.ContinuousImprovement.Biz/MappedColumns/MappedColumn.cs new file mode 100644 index 0000000..f0865b4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/MappedColumns/MappedColumn.cs @@ -0,0 +1,38 @@ +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Strata.ContinuousImprovement.Biz.MappedColumns +{ + public class MappedColumn : IMappedColumn + { + public int Index { get; set; } + public string Name { get; set; } + public string Heading { get; set; } + public bool IsLocked { get; set; } + public bool IsHidden { get; set; } + + public MappedColumn() { } + + public MappedColumn(PropertyInfo propertyInfo, int index, bool isLocked) + { + Index = index; + Name = propertyInfo.Name; + Heading = Name.SplitCamelCase(); + IsLocked = isLocked; + } + + public static IEnumerable GetListOfColumns(params string[] lockedColumns) where T : class + { + if (lockedColumns.Length == 0) + { + lockedColumns = new[] { "name" }; + } + var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select((p, i) => new MappedColumn(p, i, lockedColumns.Contains(p.Name, StringComparer.InvariantCultureIgnoreCase))); + return properties; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/MappedColumns/MappedColumnService.cs b/src/Strata.ContinuousImprovement.Biz/MappedColumns/MappedColumnService.cs new file mode 100644 index 0000000..7a37950 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/MappedColumns/MappedColumnService.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.UserSetting; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.MappedColumns +{ + public class MappedColumnService( + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IUserSettingService userSettingService + ) : IMappedColumnService + { + + public async Task> GetColumnList(CancellationToken cancellationToken) where T : class + => await GetColumnList(typeof(T).Name, cancellationToken); + + public async Task> GetColumnList(string name, CancellationToken cancellationToken) + { + var user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + var userGuid = user?.GetUserGuid() ?? Guid.Empty; + var columns = await userSettingService.GetDboUserSettings(GetName(name), userGuid, cancellationToken); + if (columns == null || !columns.Any()) + { + return GetDefaultColumnList(name); + } + return columns.OrderBy(c => c.Index); + } + + public IEnumerable GetDefaultColumnList(params string[] lockedColumns) where T : class + => MappedColumn.GetListOfColumns(lockedColumns); + + public virtual IEnumerable GetDefaultColumnList(string name, params string[] lockedColumns) + => Enumerable.Empty(); + + public async Task> GetManagedColumnList(params string[] managedColumnsToIgnore) where T : class + { + var columns = await GetColumnList(CancellationToken.None); + return columns.Where(cl => !managedColumnsToIgnore.Contains(cl.Name, StringComparer.OrdinalIgnoreCase)); + } + + public async Task SetColumnList(IEnumerable columnList, CancellationToken cancellationToken) where T : class + => await SetColumnList(typeof(T).Name, columnList, cancellationToken); + + public async Task SetColumnList(string name, IEnumerable columnList, CancellationToken cancellationToken) + { + var user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + var userGuid = user?.GetUserGuid() ?? Guid.Empty; + var reorderedColumns = columnList.Select((column, idx) => new { column, idx }).ToList(); + reorderedColumns.ForEach(reorderedColumn => reorderedColumn.column.Index = reorderedColumn.idx); + columnList = reorderedColumns.Select(reorderedColumn => reorderedColumn.column).ToList(); + var listOfColumns = columnList + .GroupBy(cl => cl.Name, (g, d) => d.OrderBy(c => c.Index).First()) + .OrderBy(c => c.Index); + await userSettingService.SetDboUserSetting(GetName(name), userGuid, listOfColumns, cancellationToken); + } + + private static string GetName(string name) => "CCI_COLUMNS_" + name.ToUpper().SplitCamelCase().Replace(" ", "_"); + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Mappers/AuthorMapper.cs b/src/Strata.ContinuousImprovement.Biz/Mappers/AuthorMapper.cs new file mode 100644 index 0000000..96263f1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Mappers/AuthorMapper.cs @@ -0,0 +1,18 @@ +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Security.Claims; + +namespace Strata.ContinuousImprovement.Biz.Mappers +{ + public static class AuthorMapper + { + + public static T MapAuthor(this T src, ClaimsPrincipal user) where T : IWithAuthor + { + if (src == null) return src; + src.AuthorGuid = user?.GetUserGuid() ?? Guid.Empty; + src.AuthorName = user?.Identity?.Name ?? string.Empty; + return src; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Mappers/ExplorationMapper.cs b/src/Strata.ContinuousImprovement.Biz/Mappers/ExplorationMapper.cs new file mode 100644 index 0000000..4fe1c7f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Mappers/ExplorationMapper.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DocumentFormat.OpenXml.Spreadsheet; +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Exploration; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Population; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes; + +namespace Strata.ContinuousImprovement.Biz.Mappers +{ + public static class ExplorationMapper + { + public static Exploration.Filters.Filter MapToExplorationFilter(this ExplorationPopulation population) + { + var dates = population.GetDischargeDates(); + + return new Exploration.Filters.Filter() + { + DischargeDateStart = dates.Item1, + DischargeDateEnd = dates.Item2, + FilterChipItems = population.FilterChipItems + }; + } + + public static Exploration.Filters.ExclusionCriteria MapToExplorationExclusionCriteria(this ExplorationPopulation population) + { + return new Exploration.Filters.ExclusionCriteria() + { + LosExclusionType = (Exploration.Filters.ExclusionCriteriaType)population.LosExclusionType, + LosExclusionValue = Decimal.ToDouble(population.LosExclusionValue), + CostExclusionType = (Exploration.Filters.ExclusionCriteriaType)population.CostExclusionType, + CostExclusionValue = Decimal.ToDouble(population.CostExclusionValue) + }; + } + + public static ExplorationDetailInfo MapToExplorationDetailInfo(this ExplorationPopulationDto population) + { + return new ExplorationDetailInfo() + { + ExclusionCriteria = population.MapToExplorationExclusionCriteria(), + Filters = population.MapToExplorationFilter(), + CaseTypeFamilyId = population.CaseTypeFamilyId, + CostDriver = population.CostDriver, + CodeType = (CaseTypeCategories)population.CodeType, + ExcludedEncounters = population.ManuallyExcludedEncounterIds, + IncludedEncounters = population.ManuallyIncludedEncounterIds + }; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Mappers/FilterHelperMapper.cs b/src/Strata.ContinuousImprovement.Biz/Mappers/FilterHelperMapper.cs new file mode 100644 index 0000000..c7da420 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Mappers/FilterHelperMapper.cs @@ -0,0 +1,24 @@ +using Strata.ContinuousImprovement.Biz.Encounter; +using Strata.Schema.Client.Dtos; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Mappers +{ + + public static partial class FilterHelperMapper + { + public static List MapToFilterHelpers(this IList src) + => src.Select(x => x.MapToFilterHelper()).ToList(); + + public static FilterHelper MapToFilterHelper(this string x) + => new() { Id = x, Text = x }; + + public static List MapToFilterHelpers(this IEnumerable src) + => src.Select(dto => dto.MapToFilterHelper()).ToList(); + + public static FilterHelper MapToFilterHelper(this HierarchyNodeDto src) + => new FilterHelper { Text = src.Name, Id = src.Path }; + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Mappers/HierarchyPathsMapper.cs b/src/Strata.ContinuousImprovement.Biz/Mappers/HierarchyPathsMapper.cs new file mode 100644 index 0000000..1a31aaa --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Mappers/HierarchyPathsMapper.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Mappers +{ + public static class HierarchyPathsMapper + { + public static string MapToHierarchyPaths(this IEnumerable src) + => JsonConvert.SerializeObject(src.Select(s => new { HierarchyPath = s })); + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Mappers/IStakeholderUser.cs b/src/Strata.ContinuousImprovement.Biz/Mappers/IStakeholderUser.cs new file mode 100644 index 0000000..52cf204 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Mappers/IStakeholderUser.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Mappers +{ + public interface IStakeholderUser + { + string Resolve(IEnumerable stakeholders); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Mappers/IWithAuthor.cs b/src/Strata.ContinuousImprovement.Biz/Mappers/IWithAuthor.cs new file mode 100644 index 0000000..2c0b4d1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Mappers/IWithAuthor.cs @@ -0,0 +1,10 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Mappers +{ + public interface IWithAuthor + { + public Guid AuthorGuid { get; set; } + public string AuthorName { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Mappers/IWithOwner.cs b/src/Strata.ContinuousImprovement.Biz/Mappers/IWithOwner.cs new file mode 100644 index 0000000..b9c0562 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Mappers/IWithOwner.cs @@ -0,0 +1,10 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Mappers +{ + public interface IWithOwner + { + public Guid OwnerGuid { get; set; } + public string OwnerName { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Mappers/OwnerMapper.cs b/src/Strata.ContinuousImprovement.Biz/Mappers/OwnerMapper.cs new file mode 100644 index 0000000..d71ee17 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Mappers/OwnerMapper.cs @@ -0,0 +1,18 @@ +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Security.Claims; + +namespace Strata.ContinuousImprovement.Biz.Mappers +{ + public static class OwnerMapper + { + + public static T MapOwner(this T src, ClaimsPrincipal user) where T : IWithOwner + { + if (src == null) return src; + src.OwnerGuid = user?.GetUserGuid() ?? Guid.Empty; + src.OwnerName = user?.Identity?.Name ?? string.Empty; + return src; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Mappers/StakeholderUser.cs b/src/Strata.ContinuousImprovement.Biz/Mappers/StakeholderUser.cs new file mode 100644 index 0000000..595325c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Mappers/StakeholderUser.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.UserService; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Strata.ContinuousImprovement.Biz.Mappers +{ + public class StakeholderUser : IStakeholderUser + { + private readonly IUserService _userService; + + public StakeholderUser(IUserService userService) + { + _userService = userService; + } + + public string Resolve(IEnumerable stakeholders) + => JsonConvert.SerializeObject( + stakeholders? + .Where(u => !string.IsNullOrEmpty(u) && Guid.TryParse(u, out var result)) + .Select(u => Guid.Parse(u)) + .Select(u => new User + { + UserGUID = u, + FullName = _userService.GetUser(u, CancellationToken.None).Result.FullName + }) ?? new List()); + + } + + public class Stakeholder + { + public Guid UserGUID { get; set; } + public string FullName { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230419173719_Initialize-Network-Opps-Tables.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230419173719_Initialize-Network-Opps-Tables.Designer.cs new file mode 100644 index 0000000..90e7a09 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230419173719_Initialize-Network-Opps-Tables.Designer.cs @@ -0,0 +1,111 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20230419173719_Initialize-Network-Opps-Tables")] + partial class InitializeNetworkOppsTables + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230419173719_Initialize-Network-Opps-Tables.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230419173719_Initialize-Network-Opps-Tables.cs new file mode 100644 index 0000000..f743354 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230419173719_Initialize-Network-Opps-Tables.cs @@ -0,0 +1,67 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class InitializeNetworkOppsTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "network_opportunities", + columns: table => new + { + network_opportunity_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "text", nullable: true), + description = table.Column(type: "text", nullable: true), + cost_driver = table.Column(type: "text", nullable: true), + pursued_organizations_count = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_network_opportunities", x => x.network_opportunity_id); + }); + + migrationBuilder.CreateTable( + name: "client_network_opportunities", + columns: table => new + { + strata_id = table.Column(type: "integer", nullable: false), + network_opportunity_id = table.Column(type: "integer", nullable: false), + identified_savings = table.Column(type: "integer", nullable: false), + average_cohort_savings = table.Column(type: "integer", nullable: false), + analytics_workbook_id = table.Column(type: "integer", nullable: false), + is_published = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_client_network_opportunities", x => new { x.strata_id, x.network_opportunity_id }); + table.ForeignKey( + name: "fk_client_network_opportunities_network_opportunities_network_", + column: x => x.network_opportunity_id, + principalTable: "network_opportunities", + principalColumn: "network_opportunity_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_client_network_opportunities_network_opportunity_id", + table: "client_network_opportunities", + column: "network_opportunity_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "client_network_opportunities"); + + migrationBuilder.DropTable( + name: "network_opportunities"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230427184132_AddGMDNTermTable.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230427184132_AddGMDNTermTable.Designer.cs new file mode 100644 index 0000000..61d3634 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230427184132_AddGMDNTermTable.Designer.cs @@ -0,0 +1,143 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20230427184132_AddGMDNTermTable")] + partial class AddGMDNTermTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("GMDNTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gmdn_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GMDNTermId")); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GMDNTermId") + .HasName("pk_gmdn_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gmdn_terms_primary_di"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230427184132_AddGMDNTermTable.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230427184132_AddGMDNTermTable.cs new file mode 100644 index 0000000..41edd5c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230427184132_AddGMDNTermTable.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddGMDNTermTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "gmdn_terms", + columns: table => new + { + gmdn_term_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + primary_di = table.Column(type: "text", nullable: true), + gmdnpt_name = table.Column(type: "character varying(150)", maxLength: 150, nullable: true), + gmdnpt_definition = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_gmdn_terms", x => x.gmdn_term_id); + }); + + migrationBuilder.CreateIndex( + name: "ix_gmdn_terms_primary_di", + table: "gmdn_terms", + column: "primary_di"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "gmdn_terms"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230505152036_AddClientNetworkOppStatus.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230505152036_AddClientNetworkOppStatus.Designer.cs new file mode 100644 index 0000000..38cf018 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230505152036_AddClientNetworkOppStatus.Designer.cs @@ -0,0 +1,147 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20230505152036_AddClientNetworkOppStatus")] + partial class AddClientNetworkOppStatus + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("GMDNTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gmdn_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GMDNTermId")); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GMDNTermId") + .HasName("pk_gmdn_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gmdn_terms_primary_di"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230505152036_AddClientNetworkOppStatus.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230505152036_AddClientNetworkOppStatus.cs new file mode 100644 index 0000000..65c758d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230505152036_AddClientNetworkOppStatus.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddClientNetworkOppStatus : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "initiative_status", + table: "client_network_opportunities", + type: "integer", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "initiative_status", + table: "client_network_opportunities"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230818191533_new-gmdn-term-columns.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230818191533_new-gmdn-term-columns.Designer.cs new file mode 100644 index 0000000..f626b70 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230818191533_new-gmdn-term-columns.Designer.cs @@ -0,0 +1,159 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20230818191533_new-gmdn-term-columns")] + partial class newgmdntermcolumns + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("GMDNTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gmdn_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GMDNTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GMDNTermId") + .HasName("pk_gmdn_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gmdn_terms_primary_di"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230818191533_new-gmdn-term-columns.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230818191533_new-gmdn-term-columns.cs new file mode 100644 index 0000000..b8a415f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230818191533_new-gmdn-term-columns.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class newgmdntermcolumns : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "gmdn_code", + table: "gmdn_terms", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "gmdn_code_status", + table: "gmdn_terms", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "implantable", + table: "gmdn_terms", + type: "boolean", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "gmdn_code", + table: "gmdn_terms"); + + migrationBuilder.DropColumn( + name: "gmdn_code_status", + table: "gmdn_terms"); + + migrationBuilder.DropColumn( + name: "implantable", + table: "gmdn_terms"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230825213849_add-opportunity-comment.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230825213849_add-opportunity-comment.Designer.cs new file mode 100644 index 0000000..4bae185 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230825213849_add-opportunity-comment.Designer.cs @@ -0,0 +1,199 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20230825213849_add-opportunity-comment")] + partial class addopportunitycomment + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("GMDNTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gmdn_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GMDNTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GMDNTermId") + .HasName("pk_gmdn_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gmdn_terms_primary_di"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20230825213849_add-opportunity-comment.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20230825213849_add-opportunity-comment.cs new file mode 100644 index 0000000..5a83888 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20230825213849_add-opportunity-comment.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using System; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class addopportunitycomment : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "comments", + columns: table => new + { + comment_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + strata_id = table.Column(type: "integer", nullable: false), + source_of_savings_id = table.Column(type: "integer", nullable: false), + opportunity_id = table.Column(type: "integer", nullable: false), + comment_text = table.Column(type: "text", nullable: true), + comment_date = table.Column(type: "timestamp with time zone", nullable: false), + author = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_comments", x => x.comment_id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "comments"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231012184133_AddInitiativeDataToNetworkOpps.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231012184133_AddInitiativeDataToNetworkOpps.Designer.cs new file mode 100644 index 0000000..8fe721a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231012184133_AddInitiativeDataToNetworkOpps.Designer.cs @@ -0,0 +1,207 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20231012184133_AddInitiativeDataToNetworkOpps")] + partial class AddInitiativeDataToNetworkOpps + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("GMDNTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gmdn_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GMDNTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GMDNTermId") + .HasName("pk_gmdn_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gmdn_terms_primary_di"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231012184133_AddInitiativeDataToNetworkOpps.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231012184133_AddInitiativeDataToNetworkOpps.cs new file mode 100644 index 0000000..546543c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231012184133_AddInitiativeDataToNetworkOpps.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddInitiativeDataToNetworkOpps : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "initiative_guid", + table: "client_network_opportunities", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "opportunity_guid", + table: "client_network_opportunities", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "initiative_guid", + table: "client_network_opportunities"); + + migrationBuilder.DropColumn( + name: "opportunity_guid", + table: "client_network_opportunities"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231019204301_jazz-62349-gudid-devices-table.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231019204301_jazz-62349-gudid-devices-table.Designer.cs new file mode 100644 index 0000000..ca73119 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231019204301_jazz-62349-gudid-devices-table.Designer.cs @@ -0,0 +1,363 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20231019204301_jazz-62349-gudid-devices-table")] + partial class jazz62349gudiddevicestable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("GMDNTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gmdn_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GMDNTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GMDNTermId") + .HasName("pk_gmdn_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gmdn_terms_primary_di"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231019204301_jazz-62349-gudid-devices-table.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231019204301_jazz-62349-gudid-devices-table.cs new file mode 100644 index 0000000..8d54784 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231019204301_jazz-62349-gudid-devices-table.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class jazz62349gudiddevicestable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "gudid_devices", + columns: table => new + { + primary_di = table.Column(type: "character varying(24)", maxLength: 24, nullable: false), + public_device_record_key = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + public_version_status = table.Column(type: "character varying(12)", maxLength: 12, nullable: true), + device_record_status = table.Column(type: "character varying(12)", maxLength: 12, nullable: true), + public_version_number = table.Column(type: "integer", nullable: true), + public_version_date = table.Column(type: "timestamp with time zone", nullable: true), + device_publish_date = table.Column(type: "timestamp with time zone", nullable: true), + device_comm_distribution_end_date = table.Column(type: "timestamp with time zone", nullable: true), + device_comm_distribution_status = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + brand_name = table.Column(type: "character varying(80)", maxLength: 80, nullable: true), + version_model_number = table.Column(type: "character varying(80)", maxLength: 80, nullable: true), + catalog_number = table.Column(type: "character varying(80)", maxLength: 80, nullable: true), + duns_number = table.Column(type: "character varying(16)", maxLength: 16, nullable: true), + company_name = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + device_count = table.Column(type: "integer", nullable: true), + device_description = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + dm_exempt = table.Column(type: "boolean", nullable: false), + premarket_exempt = table.Column(type: "boolean", nullable: false), + device_hctp = table.Column(type: "boolean", nullable: false), + device_kit = table.Column(type: "boolean", nullable: false), + device_combination_product = table.Column(type: "boolean", nullable: false), + single_use = table.Column(type: "boolean", nullable: false), + lot_batch = table.Column(type: "boolean", nullable: false), + serial_number = table.Column(type: "boolean", nullable: false), + manufacturing_date = table.Column(type: "boolean", nullable: false), + expiration_date = table.Column(type: "boolean", nullable: false), + donation_id_number = table.Column(type: "boolean", nullable: false), + labeled_contains_nrl = table.Column(type: "boolean", nullable: false), + labeled_no_nrl = table.Column(type: "boolean", nullable: false), + mri_safety_status = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + rx = table.Column(type: "boolean", nullable: false), + otc = table.Column(type: "boolean", nullable: false), + device_sterile = table.Column(type: "boolean", nullable: false), + sterilization_prior_to_use = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_gudid_devices", x => x.primary_di); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "gudid_devices"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231020151858_RenameGMDNTermsTableToGUDIDTerms.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231020151858_RenameGMDNTermsTableToGUDIDTerms.Designer.cs new file mode 100644 index 0000000..e8010fd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231020151858_RenameGMDNTermsTableToGUDIDTerms.Designer.cs @@ -0,0 +1,207 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20231020151858_RenameGMDNTermsTableToGUDIDTerms")] + partial class RenameGMDNTermsTableToGUDIDTerms + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231020151858_RenameGMDNTermsTableToGUDIDTerms.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231020151858_RenameGMDNTermsTableToGUDIDTerms.cs new file mode 100644 index 0000000..68a29ec --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231020151858_RenameGMDNTermsTableToGUDIDTerms.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class RenameGMDNTermsTableToGUDIDTerms : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey("pk_gmdn_terms", "gmdn_terms"); + migrationBuilder.DropIndex("ix_gmdn_terms_primary_di", "gmdn_terms"); + + var script = @"ALTER TABLE gmdn_terms RENAME COLUMN gmdn_term_id TO gudid_term_id; + ALTER TABLE gmdn_terms RENAME TO gudid_terms;"; + migrationBuilder.Sql(script); + + migrationBuilder.AddPrimaryKey("pk_gudid_terms", "gudid_terms", "gudid_term_id"); + migrationBuilder.CreateIndex( + name: "ix_gudid_terms_primary_di", + table: "gudid_terms", + column: "primary_di"); + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey("pk_gudid_terms", "gudid_terms"); + migrationBuilder.DropIndex("ix_gudid_terms_primary_di", "gudid_terms"); + + var script = @"ALTER TABLE gudid_terms RENAME COLUMN gudid_term_id TO gmdn_term_id; + ALTER TABLE gudid_terms RENAME TO gmdn_terms;"; + migrationBuilder.Sql(script); + + migrationBuilder.AddPrimaryKey("pk_gmdn_terms", "gmdn_terms", "gmdn_term_id"); + migrationBuilder.CreateIndex( + name: "ix_gmdn_terms_primary_di", + table: "gmdn_terms", + column: "primary_di"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231020222753_AddGMDNTables.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231020222753_AddGMDNTables.Designer.cs new file mode 100644 index 0000000..173f6e6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231020222753_AddGMDNTables.Designer.cs @@ -0,0 +1,293 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20231020222753_AddGMDNTables")] + partial class AddGMDNTables + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231020222753_AddGMDNTables.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231020222753_AddGMDNTables.cs new file mode 100644 index 0000000..9ab0739 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231020222753_AddGMDNTables.cs @@ -0,0 +1,73 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddGMDNTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "gmdn_categories", + columns: table => new + { + node_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + path = table.Column(type: "text", nullable: true), + category_code = table.Column(type: "text", nullable: true), + name = table.Column(type: "text", nullable: true), + definition = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_gmdn_categories", x => x.node_id); + }); + + migrationBuilder.CreateTable( + name: "gmdn_term_categories", + columns: table => new + { + term_code = table.Column(type: "text", nullable: false), + category_code = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_gmdn_term_categories", x => new { x.term_code, x.category_code }); + }); + + migrationBuilder.CreateTable( + name: "gmdn_terms", + columns: table => new + { + term_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + term_code = table.Column(type: "text", nullable: true), + term_name = table.Column(type: "text", nullable: true), + term_definition = table.Column(type: "text", nullable: true), + term_status = table.Column(type: "text", nullable: true), + term_is_ivd = table.Column(type: "text", nullable: true), + created_date = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_gmdn_terms", x => x.term_id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "gmdn_categories"); + + migrationBuilder.DropTable( + name: "gmdn_term_categories"); + + migrationBuilder.DropTable( + name: "gmdn_terms"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231024165350_AddPrimaryDIDimension.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231024165350_AddPrimaryDIDimension.Designer.cs new file mode 100644 index 0000000..1967c1a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231024165350_AddPrimaryDIDimension.Designer.cs @@ -0,0 +1,609 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20231024165350_AddPrimaryDIDimension")] + partial class AddPrimaryDIDimension + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DimPrimaryDI", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("HistoryItemGUID") + .HasColumnType("uuid") + .HasColumnName("history_item_guid"); + + b.Property("MemberGUID") + .HasColumnType("uuid") + .HasColumnName("member_guid"); + + b.Property("TransactionID") + .HasColumnType("integer") + .HasColumnName("transaction_id"); + + b.HasKey("PrimaryDI") + .HasName("pk_dim_primary_di"); + + b.ToTable("dim_primary_di", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231024165350_AddPrimaryDIDimension.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231024165350_AddPrimaryDIDimension.cs new file mode 100644 index 0000000..3c8f55a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231024165350_AddPrimaryDIDimension.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddPrimaryDIDimension : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "dim_primary_di", + columns: table => new + { + primary_di = table.Column(type: "text", nullable: false), + member_guid = table.Column(type: "uuid", nullable: false), + history_item_guid = table.Column(type: "uuid", nullable: false), + transaction_id = table.Column(type: "integer", nullable: false), + gmdn_term_code = table.Column(type: "text", nullable: true), + gmdn_term_name = table.Column(type: "text", nullable: true), + gmdn_term_definition = table.Column(type: "text", nullable: true), + gmdn_device_function1 = table.Column(type: "text", nullable: true), + gmdn_device_function2 = table.Column(type: "text", nullable: true), + gmdn_device_function3 = table.Column(type: "text", nullable: true), + gmdn_device_function4 = table.Column(type: "text", nullable: true), + gmdn_device_function5 = table.Column(type: "text", nullable: true), + gmdn_device_function6 = table.Column(type: "text", nullable: true), + gmdn_device_function7 = table.Column(type: "text", nullable: true), + gmdn_invasiveness1 = table.Column(type: "text", nullable: true), + gmdn_invasiveness2 = table.Column(type: "text", nullable: true), + gmdn_use_frequency1 = table.Column(type: "text", nullable: true), + gmdn_use_frequency2 = table.Column(type: "text", nullable: true), + gmdn_power1 = table.Column(type: "text", nullable: true), + gmdn_power2 = table.Column(type: "text", nullable: true), + gmdn_materials1 = table.Column(type: "text", nullable: true), + gmdn_materials2 = table.Column(type: "text", nullable: true), + gmdn_featured_attributes1 = table.Column(type: "text", nullable: true), + gmdn_featured_attributes2 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty1 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty2 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty3 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty4 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty5 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty6 = table.Column(type: "text", nullable: true), + gmdn_names_index1 = table.Column(type: "text", nullable: true), + gmdn_names_index2 = table.Column(type: "text", nullable: true), + gmdn_names_index3 = table.Column(type: "text", nullable: true), + gmdn_names_index4 = table.Column(type: "text", nullable: true), + gmdn_names_index5 = table.Column(type: "text", nullable: true), + gmdn_names_index6 = table.Column(type: "text", nullable: true), + gmdn_names_index7 = table.Column(type: "text", nullable: true), + gudid_company_name = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_dim_primary_di", x => x.primary_di); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "dim_primary_di"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231027022530_AddSavingsDescriptionColumn.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231027022530_AddSavingsDescriptionColumn.Designer.cs new file mode 100644 index 0000000..03e755a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231027022530_AddSavingsDescriptionColumn.Designer.cs @@ -0,0 +1,367 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20231027022530_AddSavingsDescriptionColumn")] + partial class AddSavingsDescriptionColumn + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231027022530_AddSavingsDescriptionColumn.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231027022530_AddSavingsDescriptionColumn.cs new file mode 100644 index 0000000..3c79d74 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231027022530_AddSavingsDescriptionColumn.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddSavingsDescriptionColumn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "identified_savings_description", + table: "client_network_opportunities", + type: "text", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "identified_savings_description", + table: "client_network_opportunities"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231106225021_AddGUDIDDeviceTermView.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231106225021_AddGUDIDDeviceTermView.Designer.cs new file mode 100644 index 0000000..f782e5a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231106225021_AddGUDIDDeviceTermView.Designer.cs @@ -0,0 +1,638 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20231106225021_AddGUDIDDeviceTermView")] + partial class AddGUDIDDeviceTermView + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DimPrimaryDI", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("HistoryItemGUID") + .HasColumnType("uuid") + .HasColumnName("history_item_guid"); + + b.Property("MemberGUID") + .HasColumnType("uuid") + .HasColumnName("member_guid"); + + b.Property("TransactionID") + .HasColumnType("integer") + .HasColumnName("transaction_id"); + + b.HasKey("PrimaryDI") + .HasName("pk_dim_primary_di"); + + b.ToTable("dim_primary_di", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDeviceTerm", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("PrimaryDI"); + + b.ToTable((string)null); + + b.ToView("gudid_device_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20231106225021_AddGUDIDDeviceTermView.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20231106225021_AddGUDIDDeviceTermView.cs new file mode 100644 index 0000000..1626939 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20231106225021_AddGUDIDDeviceTermView.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddGUDIDDeviceTermView : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + var script = @"CREATE OR REPLACE VIEW public.gudid_device_terms +AS +SELECT + d.Primary_DI, + d.Company_Name, + t.GMDN_Code, + ROW_NUMBER() OVER (PARTITION BY d.Primary_DI, d.Company_Name + ORDER BY t.GMDN_Code DESC) as Row_Number +FROM gudid_devices d +INNER JOIN gudid_terms t on d.Primary_DI = t.Primary_DI; + +ALTER TABLE public.gudid_device_terms + OWNER TO postgres;"; + migrationBuilder.Sql(script); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var script = @"DROP VIEW public.gudid_device_terms;"; + migrationBuilder.Sql(script); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240109150415_JAZZ-63722-Redo-Opp-Link-Network-Opportunities.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240109150415_JAZZ-63722-Redo-Opp-Link-Network-Opportunities.Designer.cs new file mode 100644 index 0000000..9be1717 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240109150415_JAZZ-63722-Redo-Opp-Link-Network-Opportunities.Designer.cs @@ -0,0 +1,683 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240109150415_JAZZ-63722-Redo-Opp-Link-Network-Opportunities")] + partial class JAZZ63722RedoOppLinkNetworkOpportunities + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DimPrimaryDI", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("HistoryItemGUID") + .HasColumnType("uuid") + .HasColumnName("history_item_guid"); + + b.Property("MemberGUID") + .HasColumnType("uuid") + .HasColumnName("member_guid"); + + b.Property("TransactionID") + .HasColumnType("integer") + .HasColumnName("transaction_id"); + + b.HasKey("PrimaryDI") + .HasName("pk_dim_primary_di"); + + b.ToTable("dim_primary_di", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDeviceTerm", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("PrimaryDI"); + + b.ToTable((string)null); + + b.ToView("gudid_device_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240109150415_JAZZ-63722-Redo-Opp-Link-Network-Opportunities.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240109150415_JAZZ-63722-Redo-Opp-Link-Network-Opportunities.cs new file mode 100644 index 0000000..0a06abd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240109150415_JAZZ-63722-Redo-Opp-Link-Network-Opportunities.cs @@ -0,0 +1,71 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using System; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class JAZZ63722RedoOppLinkNetworkOpportunities : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "initiative_guid", + table: "client_network_opportunities"); + + migrationBuilder.DropColumn( + name: "opportunity_guid", + table: "client_network_opportunities"); + + migrationBuilder.CreateTable( + name: "client_network_opportunity_links", + columns: table => new + { + client_network_opportunity_link_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + configuration_guid = table.Column(type: "uuid", nullable: false), + opportunity_guid = table.Column(type: "uuid", nullable: false), + initiative_guid = table.Column(type: "uuid", nullable: false), + client_network_opportunity_strata_id = table.Column(type: "integer", nullable: true), + client_network_opportunity_network_opportunity_id = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_client_network_opportunity_links", x => x.client_network_opportunity_link_id); + table.ForeignKey( + name: "fk_client_network_opportunity_links_client_network_opportuniti", + columns: x => new { x.client_network_opportunity_strata_id, x.client_network_opportunity_network_opportunity_id }, + principalTable: "client_network_opportunities", + principalColumns: new[] { "strata_id", "network_opportunity_id" }); + }); + + migrationBuilder.CreateIndex( + name: "ix_client_network_opportunity_links_client_network_opportunity", + table: "client_network_opportunity_links", + columns: new[] { "client_network_opportunity_strata_id", "client_network_opportunity_network_opportunity_id" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "client_network_opportunity_links"); + + migrationBuilder.AddColumn( + name: "initiative_guid", + table: "client_network_opportunities", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "opportunity_guid", + table: "client_network_opportunities", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212546_JAZZ-66167-FDA-GUDID-Identifiers-table.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212546_JAZZ-66167-FDA-GUDID-Identifiers-table.Designer.cs new file mode 100644 index 0000000..1e7b7bb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212546_JAZZ-66167-FDA-GUDID-Identifiers-table.Designer.cs @@ -0,0 +1,802 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240208212546_JAZZ-66167-FDA-GUDID-Identifiers-table")] + partial class JAZZ66167FDAGUDIDIdentifierstable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DimPrimaryDI", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("HistoryItemGUID") + .HasColumnType("uuid") + .HasColumnName("history_item_guid"); + + b.Property("MemberGUID") + .HasColumnType("uuid") + .HasColumnName("member_guid"); + + b.Property("TransactionID") + .HasColumnType("integer") + .HasColumnName("transaction_id"); + + b.HasKey("PrimaryDI") + .HasName("pk_dim_primary_di"); + + b.ToTable("dim_primary_di", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDeviceTerm", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("PrimaryDI"); + + b.ToTable((string)null); + + b.ToView("gudid_device_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212546_JAZZ-66167-FDA-GUDID-Identifiers-table.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212546_JAZZ-66167-FDA-GUDID-Identifiers-table.cs new file mode 100644 index 0000000..9c05789 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212546_JAZZ-66167-FDA-GUDID-Identifiers-table.cs @@ -0,0 +1,94 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using System; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class JAZZ66167FDAGUDIDIdentifierstable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "tmp"); + + migrationBuilder.CreateTable( + name: "gudid_identifiers", + columns: table => new + { + gudid_identifier_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + primary_di = table.Column(type: "text", nullable: true), + device_id = table.Column(type: "text", nullable: true), + device_id_type = table.Column(type: "text", nullable: true), + device_id_issuing_agency = table.Column(type: "text", nullable: true), + contains_di_number = table.Column(type: "text", nullable: true), + pkg_quantity = table.Column(type: "integer", nullable: false), + pkg_discontinue_date = table.Column(type: "timestamp with time zone", nullable: true), + pkg_status = table.Column(type: "text", nullable: true), + pkg_type = table.Column(type: "text", nullable: true), + is_deleted = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_gudid_identifiers", x => x.gudid_identifier_id); + }); + + migrationBuilder.CreateTable( + name: "gudid_identifiers", + schema: "tmp", + columns: table => new + { + tmp_gudid_identifier_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + primary_di = table.Column(type: "text", nullable: true), + device_id = table.Column(type: "text", nullable: true), + device_id_type = table.Column(type: "text", nullable: true), + device_id_issuing_agency = table.Column(type: "text", nullable: true), + contains_di_number = table.Column(type: "text", nullable: true), + pkg_quantity = table.Column(type: "integer", nullable: false), + pkg_discontinue_date = table.Column(type: "timestamp with time zone", nullable: true), + pkg_status = table.Column(type: "text", nullable: true), + pkg_type = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_gudid_identifiers", x => x.tmp_gudid_identifier_id); + }); + + migrationBuilder.CreateIndex( + name: "ix_gudid_identifiers_device_id", + table: "gudid_identifiers", + column: "device_id"); + + migrationBuilder.CreateIndex( + name: "ix_gudid_identifiers_device_id_primary_di", + table: "gudid_identifiers", + columns: new[] { "device_id", "primary_di" }); + + migrationBuilder.CreateIndex( + name: "ix_gudid_identifiers_device_id_primary_di_device_id_type", + table: "gudid_identifiers", + columns: new[] { "device_id", "primary_di", "device_id_type" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_gudid_identifiers_primary_di", + table: "gudid_identifiers", + column: "primary_di"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "gudid_identifiers"); + + migrationBuilder.DropTable( + name: "gudid_identifiers", + schema: "tmp"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212600_JAZZ-66167-FDA-GUDID-Identifiers-merge.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212600_JAZZ-66167-FDA-GUDID-Identifiers-merge.Designer.cs new file mode 100644 index 0000000..e0691f0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212600_JAZZ-66167-FDA-GUDID-Identifiers-merge.Designer.cs @@ -0,0 +1,802 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240208212600_JAZZ-66167-FDA-GUDID-Identifiers-merge")] + partial class JAZZ66167FDAGUDIDIdentifiersmerge + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DimPrimaryDI", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("HistoryItemGUID") + .HasColumnType("uuid") + .HasColumnName("history_item_guid"); + + b.Property("MemberGUID") + .HasColumnType("uuid") + .HasColumnName("member_guid"); + + b.Property("TransactionID") + .HasColumnType("integer") + .HasColumnName("transaction_id"); + + b.HasKey("PrimaryDI") + .HasName("pk_dim_primary_di"); + + b.ToTable("dim_primary_di", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDeviceTerm", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("PrimaryDI"); + + b.ToTable((string)null); + + b.ToView("gudid_device_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212600_JAZZ-66167-FDA-GUDID-Identifiers-merge.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212600_JAZZ-66167-FDA-GUDID-Identifiers-merge.cs new file mode 100644 index 0000000..1b1b708 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240208212600_JAZZ-66167-FDA-GUDID-Identifiers-merge.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class JAZZ66167FDAGUDIDIdentifiersmerge : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + var sqlProc = @" +CREATE OR REPLACE PROCEDURE public.update_gudid_identifiers() +LANGUAGE SQL +AS $$ +INSERT INTO public.gudid_identifiers (primary_di, device_id, device_id_type, device_id_issuing_agency, contains_di_number, pkg_quantity, pkg_discontinue_date, pkg_status, pkg_type, is_deleted) +SELECT primary_di, device_id, device_id_type, device_id_issuing_agency, contains_di_number, pkg_quantity, pkg_discontinue_date, pkg_status, pkg_type, false +FROM tmp.gudid_identifiers +ON CONFLICT (primary_di, device_id, device_id_type) DO + UPDATE SET + device_id_issuing_agency = EXCLUDED.device_id_issuing_agency, + contains_di_number = EXCLUDED.contains_di_number, + pkg_quantity = EXCLUDED.pkg_quantity, + pkg_discontinue_date = EXCLUDED.pkg_discontinue_date, + pkg_status = EXCLUDED.pkg_status, + pkg_type = EXCLUDED.pkg_type, + is_deleted = false; + +UPDATE public.gudid_identifiers + SET is_deleted = true + WHERE NOT EXISTS(select 1 + from tmp.gudid_identifiers AS gudid + where gudid.primary_di = gudid_identifiers.primary_di + and gudid.device_id = gudid_identifiers.device_id + and gudid.device_id_type = gudid_identifiers.device_id_type); +$$; +"; + migrationBuilder.Sql(sqlProc); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var dropProc = @"DROP PROCEDURE IF EXISTS public.update_gudid_identifiers();"; + migrationBuilder.Sql(dropProc); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240213150644_AddWorkbookTemplateIdToNetworkOpportunity.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240213150644_AddWorkbookTemplateIdToNetworkOpportunity.Designer.cs new file mode 100644 index 0000000..a9f3b5b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240213150644_AddWorkbookTemplateIdToNetworkOpportunity.Designer.cs @@ -0,0 +1,687 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240213150644_AddWorkbookTemplateIdToNetworkOpportunity")] + partial class AddWorkbookTemplateIdToNetworkOpportunity + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DimPrimaryDI", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("HistoryItemGUID") + .HasColumnType("uuid") + .HasColumnName("history_item_guid"); + + b.Property("MemberGUID") + .HasColumnType("uuid") + .HasColumnName("member_guid"); + + b.Property("TransactionID") + .HasColumnType("integer") + .HasColumnName("transaction_id"); + + b.HasKey("PrimaryDI") + .HasName("pk_dim_primary_di"); + + b.ToTable("dim_primary_di", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDeviceTerm", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("PrimaryDI"); + + b.ToTable((string)null); + + b.ToView("gudid_device_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240213150644_AddWorkbookTemplateIdToNetworkOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240213150644_AddWorkbookTemplateIdToNetworkOpportunity.cs new file mode 100644 index 0000000..cb2cce2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240213150644_AddWorkbookTemplateIdToNetworkOpportunity.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddWorkbookTemplateIdToNetworkOpportunity : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "workbook_template_id", + table: "network_opportunities", + type: "integer", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "workbook_template_id", + table: "network_opportunities"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240219150433_AddConstraintToGudidTempTable.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240219150433_AddConstraintToGudidTempTable.Designer.cs new file mode 100644 index 0000000..345dd71 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240219150433_AddConstraintToGudidTempTable.Designer.cs @@ -0,0 +1,806 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240219150433_AddConstraintToGudidTempTable")] + partial class AddConstraintToGudidTempTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DimPrimaryDI", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("HistoryItemGUID") + .HasColumnType("uuid") + .HasColumnName("history_item_guid"); + + b.Property("MemberGUID") + .HasColumnType("uuid") + .HasColumnName("member_guid"); + + b.Property("TransactionID") + .HasColumnType("integer") + .HasColumnName("transaction_id"); + + b.HasKey("PrimaryDI") + .HasName("pk_dim_primary_di"); + + b.ToTable("dim_primary_di", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDeviceTerm", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("PrimaryDI"); + + b.ToTable((string)null); + + b.ToView("gudid_device_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240219150433_AddConstraintToGudidTempTable.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240219150433_AddConstraintToGudidTempTable.cs new file mode 100644 index 0000000..7321a12 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240219150433_AddConstraintToGudidTempTable.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddConstraintToGudidTempTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "ix_gudid_identifiers_device_id_primary_di_device_id_type1", + schema: "tmp", + table: "gudid_identifiers", + columns: new[] { "device_id", "primary_di", "device_id_type" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "ix_gudid_identifiers_device_id_primary_di_device_id_type1", + schema: "tmp", + table: "gudid_identifiers"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240219220906_ClearAllNetworkOpportunityData.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240219220906_ClearAllNetworkOpportunityData.Designer.cs new file mode 100644 index 0000000..3973911 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240219220906_ClearAllNetworkOpportunityData.Designer.cs @@ -0,0 +1,687 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240219220906_ClearAllNetworkOpportunityData")] + partial class ClearAllNetworkOpportunityData + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DimPrimaryDI", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("HistoryItemGUID") + .HasColumnType("uuid") + .HasColumnName("history_item_guid"); + + b.Property("MemberGUID") + .HasColumnType("uuid") + .HasColumnName("member_guid"); + + b.Property("TransactionID") + .HasColumnType("integer") + .HasColumnName("transaction_id"); + + b.HasKey("PrimaryDI") + .HasName("pk_dim_primary_di"); + + b.ToTable("dim_primary_di", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDeviceTerm", b => + { + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("PrimaryDI"); + + b.ToTable((string)null); + + b.ToView("gudid_device_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240219220906_ClearAllNetworkOpportunityData.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240219220906_ClearAllNetworkOpportunityData.cs new file mode 100644 index 0000000..2c1d1cb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240219220906_ClearAllNetworkOpportunityData.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class ClearAllNetworkOpportunityData : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + var script = @"TRUNCATE TABLE public.network_opportunities RESTART IDENTITY CASCADE; +TRUNCATE TABLE public.client_network_opportunities RESTART IDENTITY CASCADE; +TRUNCATE TABLE public.client_network_opportunity_links RESTART IDENTITY; +"; + + migrationBuilder.Sql(script); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155601_AddDeviceIdentifierDimension.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155601_AddDeviceIdentifierDimension.Designer.cs new file mode 100644 index 0000000..e1d658e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155601_AddDeviceIdentifierDimension.Designer.cs @@ -0,0 +1,814 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240228155601_AddDeviceIdentifierDimension")] + partial class AddDeviceIdentifierDimension + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155601_AddDeviceIdentifierDimension.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155601_AddDeviceIdentifierDimension.cs new file mode 100644 index 0000000..8bf3046 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155601_AddDeviceIdentifierDimension.cs @@ -0,0 +1,119 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddDeviceIdentifierDimension : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "dim_primary_di"); + + migrationBuilder.CreateTable( + name: "device_identifier_dimension", + columns: table => new + { + device_identifier = table.Column(type: "text", nullable: false), + primary_di = table.Column(type: "text", nullable: true), + device_id_type = table.Column(type: "text", nullable: true), + gmdn_term_code = table.Column(type: "text", nullable: true), + gmdn_term_name = table.Column(type: "text", nullable: true), + gmdn_term_definition = table.Column(type: "text", nullable: true), + gmdn_device_function1 = table.Column(type: "text", nullable: true), + gmdn_device_function2 = table.Column(type: "text", nullable: true), + gmdn_device_function3 = table.Column(type: "text", nullable: true), + gmdn_device_function4 = table.Column(type: "text", nullable: true), + gmdn_device_function5 = table.Column(type: "text", nullable: true), + gmdn_device_function6 = table.Column(type: "text", nullable: true), + gmdn_device_function7 = table.Column(type: "text", nullable: true), + gmdn_invasiveness1 = table.Column(type: "text", nullable: true), + gmdn_invasiveness2 = table.Column(type: "text", nullable: true), + gmdn_use_frequency1 = table.Column(type: "text", nullable: true), + gmdn_use_frequency2 = table.Column(type: "text", nullable: true), + gmdn_power1 = table.Column(type: "text", nullable: true), + gmdn_power2 = table.Column(type: "text", nullable: true), + gmdn_materials1 = table.Column(type: "text", nullable: true), + gmdn_materials2 = table.Column(type: "text", nullable: true), + gmdn_featured_attributes1 = table.Column(type: "text", nullable: true), + gmdn_featured_attributes2 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty1 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty2 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty3 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty4 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty5 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty6 = table.Column(type: "text", nullable: true), + gmdn_names_index1 = table.Column(type: "text", nullable: true), + gmdn_names_index2 = table.Column(type: "text", nullable: true), + gmdn_names_index3 = table.Column(type: "text", nullable: true), + gmdn_names_index4 = table.Column(type: "text", nullable: true), + gmdn_names_index5 = table.Column(type: "text", nullable: true), + gmdn_names_index6 = table.Column(type: "text", nullable: true), + gmdn_names_index7 = table.Column(type: "text", nullable: true), + gudid_company_name = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_device_identifier_dimension", x => x.device_identifier); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "device_identifier_dimension"); + + migrationBuilder.CreateTable( + name: "dim_primary_di", + columns: table => new + { + primary_di = table.Column(type: "text", nullable: false), + gmdn_anatomical_specialty1 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty2 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty3 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty4 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty5 = table.Column(type: "text", nullable: true), + gmdn_anatomical_specialty6 = table.Column(type: "text", nullable: true), + gmdn_device_function1 = table.Column(type: "text", nullable: true), + gmdn_device_function2 = table.Column(type: "text", nullable: true), + gmdn_device_function3 = table.Column(type: "text", nullable: true), + gmdn_device_function4 = table.Column(type: "text", nullable: true), + gmdn_device_function5 = table.Column(type: "text", nullable: true), + gmdn_device_function6 = table.Column(type: "text", nullable: true), + gmdn_device_function7 = table.Column(type: "text", nullable: true), + gmdn_featured_attributes1 = table.Column(type: "text", nullable: true), + gmdn_featured_attributes2 = table.Column(type: "text", nullable: true), + gmdn_invasiveness1 = table.Column(type: "text", nullable: true), + gmdn_invasiveness2 = table.Column(type: "text", nullable: true), + gmdn_materials1 = table.Column(type: "text", nullable: true), + gmdn_materials2 = table.Column(type: "text", nullable: true), + gmdn_names_index1 = table.Column(type: "text", nullable: true), + gmdn_names_index2 = table.Column(type: "text", nullable: true), + gmdn_names_index3 = table.Column(type: "text", nullable: true), + gmdn_names_index4 = table.Column(type: "text", nullable: true), + gmdn_names_index5 = table.Column(type: "text", nullable: true), + gmdn_names_index6 = table.Column(type: "text", nullable: true), + gmdn_names_index7 = table.Column(type: "text", nullable: true), + gmdn_power1 = table.Column(type: "text", nullable: true), + gmdn_power2 = table.Column(type: "text", nullable: true), + gmdn_term_code = table.Column(type: "text", nullable: true), + gmdn_term_definition = table.Column(type: "text", nullable: true), + gmdn_term_name = table.Column(type: "text", nullable: true), + gmdn_use_frequency1 = table.Column(type: "text", nullable: true), + gmdn_use_frequency2 = table.Column(type: "text", nullable: true), + gudid_company_name = table.Column(type: "text", nullable: true), + history_item_guid = table.Column(type: "uuid", nullable: false), + member_guid = table.Column(type: "uuid", nullable: false), + transaction_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_dim_primary_di", x => x.primary_di); + }); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155632_AlterGUDIDDeviceTermsView.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155632_AlterGUDIDDeviceTermsView.Designer.cs new file mode 100644 index 0000000..666929d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155632_AlterGUDIDDeviceTermsView.Designer.cs @@ -0,0 +1,814 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240228155632_AlterGUDIDDeviceTermsView")] + partial class AlterGUDIDDeviceTermsView + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155632_AlterGUDIDDeviceTermsView.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155632_AlterGUDIDDeviceTermsView.cs new file mode 100644 index 0000000..772ee05 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240228155632_AlterGUDIDDeviceTermsView.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AlterGUDIDDeviceTermsView : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + var script = @"DROP VIEW public.gudid_device_terms; +CREATE OR REPLACE VIEW public.device_identifier_terms + AS + SELECT i.device_id, + i.device_id_type, + i.primary_di, + d.company_name, + t.gmdn_code, + row_number() OVER (PARTITION BY i.device_id + ORDER BY case i.device_id_type when 'Primary' then 0 else 1 end, + i.primary_di DESC, t.gmdn_code DESC) AS row_number + FROM gudid_identifiers i + LEFT JOIN gudid_devices d ON i.primary_di = d.primary_di::text + LEFT JOIN gudid_terms t ON d.primary_di::text = t.primary_di; + +ALTER TABLE public.device_identifier_terms + OWNER TO postgres; + +"; + + migrationBuilder.Sql(script); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var script = @"DROP VIEW public.device_identifier_terms; +CREATE OR REPLACE VIEW public.gudid_device_terms + AS + SELECT d.primary_di, + d.company_name, + t.gmdn_code, + row_number() OVER (PARTITION BY d.primary_di, d.company_name ORDER BY t.gmdn_code DESC) AS row_number + FROM gudid_devices d + JOIN gudid_terms t ON d.primary_di::text = t.primary_di; + +ALTER TABLE public.gudid_device_terms + OWNER TO postgres; +"; + + migrationBuilder.Sql(script); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240408151829_AddOpportunityWorkbooks.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240408151829_AddOpportunityWorkbooks.Designer.cs new file mode 100644 index 0000000..85b1a12 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240408151829_AddOpportunityWorkbooks.Designer.cs @@ -0,0 +1,854 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240408151829_AddOpportunityWorkbooks")] + partial class AddOpportunityWorkbooks + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240408151829_AddOpportunityWorkbooks.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240408151829_AddOpportunityWorkbooks.cs new file mode 100644 index 0000000..f2ca86d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240408151829_AddOpportunityWorkbooks.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [ExcludeFromCodeCoverage] + public partial class AddOpportunityWorkbooks : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "opportunity_type_workbooks", + columns: table => new + { + strata_id = table.Column(type: "integer", nullable: false), + opportunity_type = table.Column(type: "integer", nullable: false), + workbook_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_opportunity_type_workbooks", x => new { x.strata_id, x.opportunity_type, x.workbook_id }); + }); + + migrationBuilder.CreateTable( + name: "opportunity_workbooks", + columns: table => new + { + strata_id = table.Column(type: "integer", nullable: false), + opportunity_guid = table.Column(type: "uuid", nullable: false), + workbook_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_opportunity_workbooks", x => new { x.strata_id, x.opportunity_guid, x.workbook_id }); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "opportunity_type_workbooks"); + + migrationBuilder.DropTable( + name: "opportunity_workbooks"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240911141842_AddNullForCohortOrgCount.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240911141842_AddNullForCohortOrgCount.Designer.cs new file mode 100644 index 0000000..b9cf8fe --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240911141842_AddNullForCohortOrgCount.Designer.cs @@ -0,0 +1,854 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20240911141842_AddNullForCohortOrgCount")] + partial class AddNullForCohortOrgCount + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20240911141842_AddNullForCohortOrgCount.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20240911141842_AddNullForCohortOrgCount.cs new file mode 100644 index 0000000..2e1f90d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20240911141842_AddNullForCohortOrgCount.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + public partial class AddNullForCohortOrgCount : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "pursued_organizations_count", + table: "network_opportunities", + type: "integer", + nullable: true, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AlterColumn( + name: "average_cohort_savings", + table: "client_network_opportunities", + type: "integer", + nullable: true, + oldClrType: typeof(int), + oldType: "integer"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "pursued_organizations_count", + table: "network_opportunities", + type: "integer", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "integer", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "average_cohort_savings", + table: "client_network_opportunities", + type: "integer", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "integer", + oldNullable: true); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20241112193215_ConvertCommentOpportunityIdToBigInt.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20241112193215_ConvertCommentOpportunityIdToBigInt.Designer.cs new file mode 100644 index 0000000..a913375 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20241112193215_ConvertCommentOpportunityIdToBigInt.Designer.cs @@ -0,0 +1,855 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20241112193215_ConvertCommentOpportunityIdToBigInt")] + partial class ConvertCommentOpportunityIdToBigInt + { + /// + 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.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20241112193215_ConvertCommentOpportunityIdToBigInt.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20241112193215_ConvertCommentOpportunityIdToBigInt.cs new file mode 100644 index 0000000..311201c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20241112193215_ConvertCommentOpportunityIdToBigInt.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class ConvertCommentOpportunityIdToBigInt : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "opportunity_id", + table: "comments", + type: "bigint", + nullable: false, + oldClrType: typeof(int), + oldType: "integer"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "opportunity_id", + table: "comments", + type: "integer", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20241112194359_AddNotNullPublicDeviceRecordKey.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20241112194359_AddNotNullPublicDeviceRecordKey.Designer.cs new file mode 100644 index 0000000..fc1b369 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20241112194359_AddNotNullPublicDeviceRecordKey.Designer.cs @@ -0,0 +1,856 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20241112194359_AddNotNullPublicDeviceRecordKey")] + partial class AddNotNullPublicDeviceRecordKey + { + /// + 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.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20241112194359_AddNotNullPublicDeviceRecordKey.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20241112194359_AddNotNullPublicDeviceRecordKey.cs new file mode 100644 index 0000000..82bb778 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20241112194359_AddNotNullPublicDeviceRecordKey.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddNotNullPublicDeviceRecordKey : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "public_device_record_key", + table: "gudid_devices", + type: "character varying(50)", + maxLength: 50, + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "character varying(50)", + oldMaxLength: 50, + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "public_device_record_key", + table: "gudid_devices", + type: "character varying(50)", + maxLength: 50, + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(50)", + oldMaxLength: 50); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20241119162858_JAZZ-80222-Gudid-Device-Version-Date-Index.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20241119162858_JAZZ-80222-Gudid-Device-Version-Date-Index.Designer.cs new file mode 100644 index 0000000..dfe42db --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20241119162858_JAZZ-80222-Gudid-Device-Version-Date-Index.Designer.cs @@ -0,0 +1,859 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20241119162858_JAZZ-80222-Gudid-Device-Version-Date-Index")] + partial class JAZZ80222GudidDeviceVersionDateIndex + { + /// + 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.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("integer") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20241119162858_JAZZ-80222-Gudid-Device-Version-Date-Index.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20241119162858_JAZZ-80222-Gudid-Device-Version-Date-Index.cs new file mode 100644 index 0000000..4bdf055 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20241119162858_JAZZ-80222-Gudid-Device-Version-Date-Index.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class JAZZ80222GudidDeviceVersionDateIndex : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "ix_gudid_devices_public_version_date", + table: "gudid_devices", + column: "public_version_date"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "ix_gudid_devices_public_version_date", + table: "gudid_devices"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250411154907_AddClientWarehouse.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250411154907_AddClientWarehouse.Designer.cs new file mode 100644 index 0000000..9a4bd6e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250411154907_AddClientWarehouse.Designer.cs @@ -0,0 +1,890 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20250411154907_AddClientWarehouse")] + partial class AddClientWarehouse + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250411154907_AddClientWarehouse.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250411154907_AddClientWarehouse.cs new file mode 100644 index 0000000..6eacaa6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250411154907_AddClientWarehouse.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using System; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddClientWarehouse : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "client_warehouses", + columns: table => new + { + client_warehouse_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + database_guid = table.Column(type: "uuid", nullable: false), + client_name = table.Column(type: "text", nullable: true), + warehouse_size = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_client_warehouses", x => x.client_warehouse_id); + }); + + migrationBuilder.CreateIndex( + name: "ix_client_warehouses_database_guid", + table: "client_warehouses", + column: "database_guid", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "client_warehouses"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250618140334_AddGudidDeviceIndex.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250618140334_AddGudidDeviceIndex.Designer.cs new file mode 100644 index 0000000..3059c2a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250618140334_AddGudidDeviceIndex.Designer.cs @@ -0,0 +1,893 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20250618140334_AddGudidDeviceIndex")] + partial class AddGudidDeviceIndex + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250618140334_AddGudidDeviceIndex.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250618140334_AddGudidDeviceIndex.cs new file mode 100644 index 0000000..ac1ae70 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250618140334_AddGudidDeviceIndex.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddGudidDeviceIndex : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "ix_gudid_devices_primary_di", + table: "gudid_devices", + column: "primary_di"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "ix_gudid_devices_primary_di", + table: "gudid_devices"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250618142121_AddExplorationPopulationTables.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250618142121_AddExplorationPopulationTables.Designer.cs new file mode 100644 index 0000000..00551fb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250618142121_AddExplorationPopulationTables.Designer.cs @@ -0,0 +1,1028 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20250618142121_AddExplorationPopulationTables")] + partial class AddExplorationPopulationTables + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusions", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounters", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulations", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusions", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulations", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounters", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulations", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulations", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250618142121_AddExplorationPopulationTables.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250618142121_AddExplorationPopulationTables.cs new file mode 100644 index 0000000..693276d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250618142121_AddExplorationPopulationTables.cs @@ -0,0 +1,91 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddExplorationPopulationTables : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "exploration_populations", + columns: table => new + { + exploration_population_id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "text", nullable: true), + case_type_family_id = table.Column(type: "integer", nullable: false), + cost_driver = table.Column(type: "text", nullable: true), + los_exclusion_type = table.Column(type: "smallint", nullable: false), + los_exclusion_value = table.Column(type: "numeric", nullable: false), + cost_exclusion_type = table.Column(type: "smallint", nullable: false), + cost_exclusion_value = table.Column(type: "numeric", nullable: false), + filters_json = table.Column(type: "text", nullable: true), + total_encounters = table.Column(type: "integer", nullable: false), + total_variation = table.Column(type: "numeric", nullable: false), + code_type = table.Column(type: "smallint", nullable: false), + strata_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_exploration_populations", x => x.exploration_population_id); + }); + + migrationBuilder.CreateTable( + name: "exploration_inclusion_exclusions", + columns: table => new + { + exploration_population_id = table.Column(type: "bigint", nullable: false), + encounter_id = table.Column(type: "bigint", nullable: false), + is_excluded = table.Column(type: "boolean", nullable: false), + strata_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_exploration_inclusion_exclusions", x => new { x.exploration_population_id, x.encounter_id }); + table.ForeignKey( + name: "fk_exploration_inclusion_exclusions_exploration_populations_ex", + column: x => x.exploration_population_id, + principalTable: "exploration_populations", + principalColumn: "exploration_population_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "exploration_population_encounters", + columns: table => new + { + exploration_population_id = table.Column(type: "bigint", nullable: false), + encounter_id = table.Column(type: "bigint", nullable: false), + strata_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_exploration_population_encounters", x => new { x.exploration_population_id, x.encounter_id }); + table.ForeignKey( + name: "fk_exploration_population_encounters_exploration_populations_e", + column: x => x.exploration_population_id, + principalTable: "exploration_populations", + principalColumn: "exploration_population_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "exploration_inclusion_exclusions"); + + migrationBuilder.DropTable( + name: "exploration_population_encounters"); + + migrationBuilder.DropTable( + name: "exploration_populations"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250806154249_AddStrategicOpportunityTables.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250806154249_AddStrategicOpportunityTables.Designer.cs new file mode 100644 index 0000000..84cb97a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250806154249_AddStrategicOpportunityTables.Designer.cs @@ -0,0 +1,1230 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20250806154249_AddStrategicOpportunityTables")] + partial class AddStrategicOpportunityTables + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StandardDataSourceId") + .HasColumnType("integer") + .HasColumnName("standard_data_source_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250806154249_AddStrategicOpportunityTables.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250806154249_AddStrategicOpportunityTables.cs new file mode 100644 index 0000000..bb8030f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250806154249_AddStrategicOpportunityTables.cs @@ -0,0 +1,144 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + /// Migration to add Strategic Opportunity tables. + /// + public partial class AddStrategicOpportunityTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + CreateStrategicOpportunityTable(migrationBuilder); + CreateStrategicOpportunityChargeCodeTable(migrationBuilder); + CreateStrategicOpportunityMeasureTable(migrationBuilder); + CreateStrategicOpportunityWorkbookTable(migrationBuilder); + } + + private void CreateStrategicOpportunityTable(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "strategic_opportunity", + columns: table => new + { + opportunity_id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "text", nullable: true), + opportunity_type = table.Column(type: "smallint", nullable: false), + opportunity_filters_json = table.Column(type: "text", nullable: true), + rollup1id = table.Column(type: "integer", nullable: false), + rollup2id = table.Column(type: "integer", nullable: false), + rollup3id = table.Column(type: "integer", nullable: false), + baseline_type = table.Column(type: "smallint", nullable: false), + baseline_distribution = table.Column(type: "smallint", nullable: false), + baseline_start_date = table.Column(type: "date", nullable: false), + baseline_end_date = table.Column(type: "date", nullable: false), + estimated_tracking_duration = table.Column(type: "smallint", nullable: false), + strata_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_strategic_opportunity", x => x.opportunity_id); + }); + } + + private void CreateStrategicOpportunityChargeCodeTable(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "strategic_opportunity_charge_code", + columns: table => new + { + opportunity_id = table.Column(type: "bigint", nullable: false), + patient_population_h_path = table.Column(type: "text", nullable: true), + exploration_population_id = table.Column(type: "bigint", nullable: false), + charge_code_h_path = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_strategic_opportunity_charge_code", x => x.opportunity_id); + table.ForeignKey( + name: "fk_strategic_opportunity_charge_code_strategic_opportunity_opp", + column: x => x.opportunity_id, + principalTable: "strategic_opportunity", + principalColumn: "opportunity_id", + onDelete: ReferentialAction.Cascade); + }); + } + + private void CreateStrategicOpportunityMeasureTable(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "strategic_opportunity_measure", + columns: table => new + { + measure_id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + opportunity_id = table.Column(type: "bigint", nullable: false), + standard_data_source_id = table.Column(type: "integer", nullable: false), + measure_type = table.Column(type: "smallint", nullable: false), + measure_name = table.Column(type: "text", nullable: true), + desired_result = table.Column(type: "smallint", nullable: false), + acceptable_threshold = table.Column(type: "real", nullable: false), + filters_json = table.Column(type: "text", nullable: true), + is_primary_measure = table.Column(type: "boolean", nullable: false), + strata_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_strategic_opportunity_measure", x => x.measure_id); + table.ForeignKey( + name: "fk_strategic_opportunity_measure_strategic_opportunity_opportu", + column: x => x.opportunity_id, + principalTable: "strategic_opportunity", + principalColumn: "opportunity_id", + onDelete: ReferentialAction.Cascade); + }); + } + + private void CreateStrategicOpportunityWorkbookTable(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "strategic_opportunity_workbook", + columns: table => new + { + opportunity_id = table.Column(type: "bigint", nullable: false), + workbook_id = table.Column(type: "integer", nullable: false), + strata_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_strategic_opportunity_workbook", x => new { x.opportunity_id, x.workbook_id }); + table.ForeignKey( + name: "fk_strategic_opportunity_workbook_strategic_opportunity_opport", + column: x => x.opportunity_id, + principalTable: "strategic_opportunity", + principalColumn: "opportunity_id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_strategic_opportunity_measure_opportunity_id", + table: "strategic_opportunity_measure", + column: "opportunity_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "strategic_opportunity_charge_code"); + + migrationBuilder.DropTable( + name: "strategic_opportunity_measure"); + + migrationBuilder.DropTable( + name: "strategic_opportunity_workbook"); + + migrationBuilder.DropTable( + name: "strategic_opportunity"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250818091715_AddLayoutJSONColumnToStrategicOpportunityTable.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250818091715_AddLayoutJSONColumnToStrategicOpportunityTable.Designer.cs new file mode 100644 index 0000000..dbfb817 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250818091715_AddLayoutJSONColumnToStrategicOpportunityTable.Designer.cs @@ -0,0 +1,1234 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20250818091715_AddLayoutJSONColumnToStrategicOpportunityTable")] + partial class AddLayoutJSONColumnToStrategicOpportunityTable + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StandardDataSourceId") + .HasColumnType("integer") + .HasColumnName("standard_data_source_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250818091715_AddLayoutJSONColumnToStrategicOpportunityTable.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250818091715_AddLayoutJSONColumnToStrategicOpportunityTable.cs new file mode 100644 index 0000000..21d49be --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250818091715_AddLayoutJSONColumnToStrategicOpportunityTable.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddLayoutJSONColumnToStrategicOpportunityTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "layout_json", + table: "strategic_opportunity", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "layout_json", + table: "strategic_opportunity"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250827094039_Added Strategic Opportunity Ramp Up table.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250827094039_Added Strategic Opportunity Ramp Up table.Designer.cs new file mode 100644 index 0000000..c14f138 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250827094039_Added Strategic Opportunity Ramp Up table.Designer.cs @@ -0,0 +1,1268 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20250827094039_Added Strategic Opportunity Ramp Up table")] + partial class AddedStrategicOpportunityRampUptable + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StandardDataSourceId") + .HasColumnType("integer") + .HasColumnName("standard_data_source_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250827094039_Added Strategic Opportunity Ramp Up table.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250827094039_Added Strategic Opportunity Ramp Up table.cs new file mode 100644 index 0000000..5bb605f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250827094039_Added Strategic Opportunity Ramp Up table.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddedStrategicOpportunityRampUptable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "strategic_opportunity_ramp_up", + columns: table => new + { + opportunity_id = table.Column(type: "bigint", nullable: false), + month_number = table.Column(type: "smallint", nullable: false), + percentage = table.Column(type: "double precision", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_strategic_opportunity_ramp_up", x => new { x.opportunity_id, x.month_number }); + table.ForeignKey( + name: "fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu", + column: x => x.opportunity_id, + principalTable: "strategic_opportunity", + principalColumn: "opportunity_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "strategic_opportunity_ramp_up"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250911101011_AddOpportunityRoleAssignmentTable.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250911101011_AddOpportunityRoleAssignmentTable.Designer.cs new file mode 100644 index 0000000..063aa47 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250911101011_AddOpportunityRoleAssignmentTable.Designer.cs @@ -0,0 +1,1304 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20250911101011_AddOpportunityRoleAssignmentTable")] + partial class AddOpportunityRoleAssignmentTable + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StandardDataSourceId") + .HasColumnType("integer") + .HasColumnName("standard_data_source_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250911101011_AddOpportunityRoleAssignmentTable.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250911101011_AddOpportunityRoleAssignmentTable.cs new file mode 100644 index 0000000..2bff044 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250911101011_AddOpportunityRoleAssignmentTable.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddOpportunityRoleAssignmentTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "opportunity_role_assignments", + columns: table => new + { + opportunity_id = table.Column(type: "bigint", nullable: false), + identity_guid = table.Column(type: "uuid", nullable: false), + strata_id = table.Column(type: "integer", nullable: false), + identity_name = table.Column(type: "text", nullable: true), + is_read = table.Column(type: "boolean", nullable: false), + is_write = table.Column(type: "boolean", nullable: false), + is_secure_write = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_opportunity_role_assignments", x => new { x.opportunity_id, x.identity_guid }); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "opportunity_role_assignments"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250917110126_Added status column to StrategicOpportunity.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250917110126_Added status column to StrategicOpportunity.Designer.cs new file mode 100644 index 0000000..b156314 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250917110126_Added status column to StrategicOpportunity.Designer.cs @@ -0,0 +1,1308 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20250917110126_Added status column to StrategicOpportunity")] + partial class AddedstatuscolumntoStrategicOpportunity + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StandardDataSourceId") + .HasColumnType("integer") + .HasColumnName("standard_data_source_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250917110126_Added status column to StrategicOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250917110126_Added status column to StrategicOpportunity.cs new file mode 100644 index 0000000..38e71c4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250917110126_Added status column to StrategicOpportunity.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddedstatuscolumntoStrategicOpportunity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "status", + table: "strategic_opportunity", + type: "smallint", + nullable: false, + defaultValue: (byte)0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "status", + table: "strategic_opportunity"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250917151411_Add tracking dates to StrategicOpportunity.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250917151411_Add tracking dates to StrategicOpportunity.Designer.cs new file mode 100644 index 0000000..b32c186 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250917151411_Add tracking dates to StrategicOpportunity.Designer.cs @@ -0,0 +1,1316 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20250917151411_Add tracking dates to StrategicOpportunity")] + partial class AddtrackingdatestoStrategicOpportunity + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StandardDataSourceId") + .HasColumnType("integer") + .HasColumnName("standard_data_source_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20250917151411_Add tracking dates to StrategicOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20250917151411_Add tracking dates to StrategicOpportunity.cs new file mode 100644 index 0000000..3f330a9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20250917151411_Add tracking dates to StrategicOpportunity.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddtrackingdatestoStrategicOpportunity : Migration + { + private const string TableName = "strategic_opportunity"; + private static readonly DateOnly DefaultDate = new(1, 1, 1); + + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + AddDateColumn(migrationBuilder, "tracking_start_date"); + AddDateColumn(migrationBuilder, "tracking_end_date"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + RemoveColumn(migrationBuilder, "tracking_start_date"); + RemoveColumn(migrationBuilder, "tracking_end_date"); + } + + private static void AddDateColumn(MigrationBuilder builder, string columnName) + { + builder.AddColumn( + name: columnName, + table: TableName, + type: "date", + nullable: false, + defaultValue: DefaultDate); + } + + private static void RemoveColumn(MigrationBuilder builder, string columnName) + { + builder.DropColumn( + name: columnName, + table: TableName); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251105230652_AddTotalValuesToStrategicOpportunity.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251105230652_AddTotalValuesToStrategicOpportunity.Designer.cs new file mode 100644 index 0000000..5c95721 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251105230652_AddTotalValuesToStrategicOpportunity.Designer.cs @@ -0,0 +1,1324 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20251105230652_AddTotalValuesToStrategicOpportunity")] + partial class AddTotalValuesToStrategicOpportunity + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalGoal") + .HasColumnType("double precision") + .HasColumnName("total_goal"); + + b.Property("TotalImprovements") + .HasColumnType("double precision") + .HasColumnName("total_improvements"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StandardDataSourceId") + .HasColumnType("integer") + .HasColumnName("standard_data_source_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251105230652_AddTotalValuesToStrategicOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251105230652_AddTotalValuesToStrategicOpportunity.cs new file mode 100644 index 0000000..240b953 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251105230652_AddTotalValuesToStrategicOpportunity.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddTotalValuesToStrategicOpportunity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "estimated_goal", + table: "strategic_opportunity", + type: "double precision", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "total_improvement", + table: "strategic_opportunity", + type: "double precision", + nullable: false, + defaultValue: 0.0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "estimated_goal", + table: "strategic_opportunity"); + + migrationBuilder.DropColumn( + name: "total_improvement", + table: "strategic_opportunity"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251117115508_Remove-StandardDataSource-From-StrategicOpportunityMeasure.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251117115508_Remove-StandardDataSource-From-StrategicOpportunityMeasure.Designer.cs new file mode 100644 index 0000000..e9a7bed --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251117115508_Remove-StandardDataSource-From-StrategicOpportunityMeasure.Designer.cs @@ -0,0 +1,1312 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20251117115508_Remove-StandardDataSource-From-StrategicOpportunityMeasure")] + partial class RemoveStandardDataSourceFromStrategicOpportunityMeasure + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251117115508_Remove-StandardDataSource-From-StrategicOpportunityMeasure.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251117115508_Remove-StandardDataSource-From-StrategicOpportunityMeasure.cs new file mode 100644 index 0000000..6e2194a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251117115508_Remove-StandardDataSource-From-StrategicOpportunityMeasure.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class RemoveStandardDataSourceFromStrategicOpportunityMeasure : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "standard_data_source_id", + table: "strategic_opportunity_measure"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "standard_data_source_id", + table: "strategic_opportunity_measure", + type: "integer", + nullable: false, + defaultValue: 0); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251202115024_AddedStrategicStakeholdersLinkTable.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251202115024_AddedStrategicStakeholdersLinkTable.Designer.cs new file mode 100644 index 0000000..b0cad79 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251202115024_AddedStrategicStakeholdersLinkTable.Designer.cs @@ -0,0 +1,1359 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20251202115024_AddedStrategicStakeholdersLinkTable")] + partial class AddedStrategicStakeholdersLinkTable + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGUID") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGUID") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGUID") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251202115024_AddedStrategicStakeholdersLinkTable.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251202115024_AddedStrategicStakeholdersLinkTable.cs new file mode 100644 index 0000000..eb2e29b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251202115024_AddedStrategicStakeholdersLinkTable.cs @@ -0,0 +1,84 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddedStrategicStakeholdersLinkTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "cost_leader_guid", + table: "strategic_opportunity", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "executive_sponsor_guid", + table: "strategic_opportunity", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "owner_guid", + table: "strategic_opportunity", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "team_members", + table: "strategic_opportunity", + type: "character varying(4000)", + maxLength: 4000, + nullable: true); + + migrationBuilder.CreateTable( + name: "strategic_stakeholders_link", + columns: table => new + { + strategic_opportunity_id = table.Column(type: "bigint", nullable: false), + user_guid = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_strategic_stakeholders_link", x => new { x.strategic_opportunity_id, x.user_guid }); + table.ForeignKey( + name: "fk_strategic_stakeholders_link_strategic_opportunity_strategic", + column: x => x.strategic_opportunity_id, + principalTable: "strategic_opportunity", + principalColumn: "opportunity_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "strategic_stakeholders_link"); + + migrationBuilder.DropColumn( + name: "cost_leader_guid", + table: "strategic_opportunity"); + + migrationBuilder.DropColumn( + name: "executive_sponsor_guid", + table: "strategic_opportunity"); + + migrationBuilder.DropColumn( + name: "owner_guid", + table: "strategic_opportunity"); + + migrationBuilder.DropColumn( + name: "team_members", + table: "strategic_opportunity"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251212174231_ClientNetworkOpportunity.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251212174231_ClientNetworkOpportunity.Designer.cs new file mode 100644 index 0000000..31fdd2c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251212174231_ClientNetworkOpportunity.Designer.cs @@ -0,0 +1,1359 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20251212174231_ClientNetworkOpportunity")] + partial class ClientNetworkOpportunity + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGuid") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedGoal") + .HasColumnType("double precision") + .HasColumnName("estimated_goal"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGuid") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGuid") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TotalImprovement") + .HasColumnType("double precision") + .HasColumnName("total_improvement"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251212174231_ClientNetworkOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251212174231_ClientNetworkOpportunity.cs new file mode 100644 index 0000000..cab1a12 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251212174231_ClientNetworkOpportunity.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class ClientNetworkOpportunity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "pursued_organizations_count", + table: "network_opportunities"); + + migrationBuilder.DropColumn( + name: "average_cohort_savings", + table: "client_network_opportunities"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "pursued_organizations_count", + table: "network_opportunities", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "average_cohort_savings", + table: "client_network_opportunities", + type: "integer", + nullable: true); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251218135025_AddRunTrackingAndTotalCommittedCostColumns.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251218135025_AddRunTrackingAndTotalCommittedCostColumns.Designer.cs new file mode 100644 index 0000000..09d9a7e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251218135025_AddRunTrackingAndTotalCommittedCostColumns.Designer.cs @@ -0,0 +1,1371 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20251218135025_AddRunTrackingAndTotalCommittedCostColumns")] + partial class AddRunTrackingAndTotalCommittedCostColumns + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("AverageCohortSavings") + .HasColumnType("integer") + .HasColumnName("average_cohort_savings"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PursuedOrganizationsCount") + .HasColumnType("integer") + .HasColumnName("pursued_organizations_count"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGuid") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGuid") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LastRunTrackingDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_run_tracking_date"); + + b.Property("LastRunTrackingStatus") + .HasColumnType("smallint") + .HasColumnName("last_run_tracking_status"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGuid") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251218135025_AddRunTrackingAndTotalCommittedCostColumns.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251218135025_AddRunTrackingAndTotalCommittedCostColumns.cs new file mode 100644 index 0000000..13baaab --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251218135025_AddRunTrackingAndTotalCommittedCostColumns.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddRunTrackingAndTotalCommittedCostColumns : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "total_committed_cost", + table: "strategic_opportunity_charge_code", + type: "double precision", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "last_run_tracking_date", + table: "strategic_opportunity", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "last_run_tracking_status", + table: "strategic_opportunity", + type: "smallint", + nullable: false, + defaultValue: (byte)0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "total_committed_cost", + table: "strategic_opportunity_charge_code"); + + migrationBuilder.DropColumn( + name: "last_run_tracking_date", + table: "strategic_opportunity"); + + migrationBuilder.DropColumn( + name: "last_run_tracking_status", + table: "strategic_opportunity"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251230164627_AddProgressToDateColumnsToStrategicOpportunityChargeCodeTable.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251230164627_AddProgressToDateColumnsToStrategicOpportunityChargeCodeTable.Designer.cs new file mode 100644 index 0000000..c48c55f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251230164627_AddProgressToDateColumnsToStrategicOpportunityChargeCodeTable.Designer.cs @@ -0,0 +1,1379 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20251230164627_AddProgressToDateColumnsToStrategicOpportunityChargeCodeTable")] + partial class AddProgressToDateColumnsToStrategicOpportunityChargeCodeTable + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGuid") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedGoal") + .HasColumnType("double precision") + .HasColumnName("estimated_goal"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGuid") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LastRunTrackingDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_run_tracking_date"); + + b.Property("LastRunTrackingStatus") + .HasColumnType("smallint") + .HasColumnName("last_run_tracking_status"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGuid") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TotalImprovement") + .HasColumnType("double precision") + .HasColumnName("total_improvement"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.Property("ProgressToDateActual") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_actual"); + + b.Property("ProgressToDateCommitted") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_committed"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20251230164627_AddProgressToDateColumnsToStrategicOpportunityChargeCodeTable.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20251230164627_AddProgressToDateColumnsToStrategicOpportunityChargeCodeTable.cs new file mode 100644 index 0000000..c6f49d5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20251230164627_AddProgressToDateColumnsToStrategicOpportunityChargeCodeTable.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddProgressToDateColumnsToStrategicOpportunityChargeCodeTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "progress_to_date_actual", + table: "strategic_opportunity_charge_code", + type: "double precision", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "progress_to_date_committed", + table: "strategic_opportunity_charge_code", + type: "double precision", + nullable: false, + defaultValue: 0.0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "progress_to_date_actual", + table: "strategic_opportunity_charge_code"); + + migrationBuilder.DropColumn( + name: "progress_to_date_committed", + table: "strategic_opportunity_charge_code"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260127142153_AddAverageValueToStrategicOpportunityMeasure.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260127142153_AddAverageValueToStrategicOpportunityMeasure.Designer.cs new file mode 100644 index 0000000..7e1c92e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260127142153_AddAverageValueToStrategicOpportunityMeasure.Designer.cs @@ -0,0 +1,1383 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20260127142153_AddAverageValueToStrategicOpportunityMeasure")] + partial class AddAverageValueToStrategicOpportunityMeasure + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGuid") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedGoal") + .HasColumnType("double precision") + .HasColumnName("estimated_goal"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGuid") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LastRunTrackingDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_run_tracking_date"); + + b.Property("LastRunTrackingStatus") + .HasColumnType("smallint") + .HasColumnName("last_run_tracking_status"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGuid") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TotalImprovement") + .HasColumnType("double precision") + .HasColumnName("total_improvement"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("AverageValue") + .HasColumnType("double precision") + .HasColumnName("average_value"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("IsPrimaryMeasure") + .HasColumnType("boolean") + .HasColumnName("is_primary_measure"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.Property("ProgressToDateActual") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_actual"); + + b.Property("ProgressToDateCommitted") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_committed"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260127142153_AddAverageValueToStrategicOpportunityMeasure.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260127142153_AddAverageValueToStrategicOpportunityMeasure.cs new file mode 100644 index 0000000..bf7dd06 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260127142153_AddAverageValueToStrategicOpportunityMeasure.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddAverageValueToStrategicOpportunityMeasure : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "average_value", + table: "strategic_opportunity_measure", + type: "double precision", + nullable: false, + defaultValue: 0.0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "average_value", + table: "strategic_opportunity_measure"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260226123152_AddMeasureGroupAndDeleteIsPrimaryFromStrategicOpportunityMeasure.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260226123152_AddMeasureGroupAndDeleteIsPrimaryFromStrategicOpportunityMeasure.Designer.cs new file mode 100644 index 0000000..3237b3f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260226123152_AddMeasureGroupAndDeleteIsPrimaryFromStrategicOpportunityMeasure.Designer.cs @@ -0,0 +1,1383 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20260226123152_AddMeasureGroupAndDeleteIsPrimaryFromStrategicOpportunityMeasure")] + partial class AddMeasureGroupAndDeleteIsPrimaryFromStrategicOpportunityMeasure + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGuid") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedGoal") + .HasColumnType("double precision") + .HasColumnName("estimated_goal"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGuid") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LastRunTrackingDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_run_tracking_date"); + + b.Property("LastRunTrackingStatus") + .HasColumnType("smallint") + .HasColumnName("last_run_tracking_status"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGuid") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TotalImprovement") + .HasColumnType("double precision") + .HasColumnName("total_improvement"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("AverageValue") + .HasColumnType("double precision") + .HasColumnName("average_value"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("MeasureGroup") + .HasColumnType("smallint") + .HasColumnName("measure_group"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.Property("ProgressToDateActual") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_actual"); + + b.Property("ProgressToDateCommitted") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_committed"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260226123152_AddMeasureGroupAndDeleteIsPrimaryFromStrategicOpportunityMeasure.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260226123152_AddMeasureGroupAndDeleteIsPrimaryFromStrategicOpportunityMeasure.cs new file mode 100644 index 0000000..60c0091 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260226123152_AddMeasureGroupAndDeleteIsPrimaryFromStrategicOpportunityMeasure.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddMeasureGroupAndDeleteIsPrimaryFromStrategicOpportunityMeasure : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "measure_group", + table: "strategic_opportunity_measure", + type: "smallint", + nullable: false, + defaultValue: (byte)0); + + migrationBuilder.Sql($"UPDATE strategic_opportunity_measure SET measure_group = {(byte)MeasureGroup.Primary} WHERE is_primary_measure = true"); + migrationBuilder.Sql($"UPDATE strategic_opportunity_measure SET measure_group = {(byte)MeasureGroup.Secondary} WHERE is_primary_measure = false"); + + migrationBuilder.DropColumn( + name: "is_primary_measure", + table: "strategic_opportunity_measure"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "is_primary_measure", + table: "strategic_opportunity_measure", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.Sql($"UPDATE strategic_opportunity_measure SET is_primary_measure = true WHERE measure_group = {(byte)MeasureGroup.Primary}"); + migrationBuilder.Sql($"UPDATE strategic_opportunity_measure SET is_primary_measure = false WHERE measure_group != {(byte)MeasureGroup.Primary}"); + + migrationBuilder.DropColumn( + name: "measure_group", + table: "strategic_opportunity_measure"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260307052555_AddStrategicOpportunityGLTable.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260307052555_AddStrategicOpportunityGLTable.Designer.cs new file mode 100644 index 0000000..0b41bc3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260307052555_AddStrategicOpportunityGLTable.Designer.cs @@ -0,0 +1,1424 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20260307052555_AddStrategicOpportunityGLTable")] + partial class AddStrategicOpportunityGLTable + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGuid") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedGoal") + .HasColumnType("double precision") + .HasColumnName("estimated_goal"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGuid") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LastRunTrackingDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_run_tracking_date"); + + b.Property("LastRunTrackingStatus") + .HasColumnType("smallint") + .HasColumnName("last_run_tracking_status"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGuid") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TotalImprovement") + .HasColumnType("double precision") + .HasColumnName("total_improvement"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("AverageValue") + .HasColumnType("double precision") + .HasColumnName("average_value"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("MeasureGroup") + .HasColumnType("smallint") + .HasColumnName("measure_group"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.Property("ProgressToDateActual") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_actual"); + + b.Property("ProgressToDateCommitted") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_committed"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("AccountType") + .HasColumnType("smallint") + .HasColumnName("account_type"); + + b.Property("DepartmentHPath") + .HasColumnType("text") + .HasColumnName("department_h_path"); + + b.Property("ExpenseAccountHPath") + .HasColumnType("text") + .HasColumnName("expense_account_h_path"); + + b.Property("HasStatistic") + .HasColumnType("boolean") + .HasColumnName("has_statistic"); + + b.Property("RevenueAccountHPath") + .HasColumnType("text") + .HasColumnName("revenue_account_h_path"); + + b.Property("StatisticAccountHPath") + .HasColumnType("text") + .HasColumnName("statistic_account_h_path"); + + b.ToTable("strategic_opportunity_gl"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_gl_strategic_opportunity_opportunity_"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260307052555_AddStrategicOpportunityGLTable.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260307052555_AddStrategicOpportunityGLTable.cs new file mode 100644 index 0000000..e95b036 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260307052555_AddStrategicOpportunityGLTable.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddStrategicOpportunityGLTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "strategic_opportunity_gl", + columns: table => new + { + opportunity_id = table.Column(type: "bigint", nullable: false), + account_type = table.Column(type: "smallint", nullable: false), + department_h_path = table.Column(type: "text", nullable: true), + revenue_account_h_path = table.Column(type: "text", nullable: true), + expense_account_h_path = table.Column(type: "text", nullable: true), + statistic_account_h_path = table.Column(type: "text", nullable: true), + has_statistic = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_strategic_opportunity_gl", x => x.opportunity_id); + table.ForeignKey( + name: "fk_strategic_opportunity_gl_strategic_opportunity_opportunity_", + column: x => x.opportunity_id, + principalTable: "strategic_opportunity", + principalColumn: "opportunity_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "strategic_opportunity_gl"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260317201612_SeparateStrategicOpportunityTypes.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260317201612_SeparateStrategicOpportunityTypes.Designer.cs new file mode 100644 index 0000000..2b38b62 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260317201612_SeparateStrategicOpportunityTypes.Designer.cs @@ -0,0 +1,1424 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20260317201612_SeparateStrategicOpportunityTypes")] + partial class SeparateStrategicOpportunityTypes + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGuid") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedGoal") + .HasColumnType("double precision") + .HasColumnName("estimated_goal"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGuid") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LastRunTrackingDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_run_tracking_date"); + + b.Property("LastRunTrackingStatus") + .HasColumnType("smallint") + .HasColumnName("last_run_tracking_status"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGuid") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TotalImprovement") + .HasColumnType("double precision") + .HasColumnName("total_improvement"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("AverageValue") + .HasColumnType("double precision") + .HasColumnName("average_value"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("MeasureGroup") + .HasColumnType("smallint") + .HasColumnName("measure_group"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.Property("ProgressToDateActual") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_actual"); + + b.Property("ProgressToDateCommitted") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_committed"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("AccountType") + .HasColumnType("smallint") + .HasColumnName("account_type"); + + b.Property("DepartmentHPath") + .HasColumnType("text") + .HasColumnName("department_h_path"); + + b.Property("ExpenseAccountHPath") + .HasColumnType("text") + .HasColumnName("expense_account_h_path"); + + b.Property("HasStatistic") + .HasColumnType("boolean") + .HasColumnName("has_statistic"); + + b.Property("RevenueAccountHPath") + .HasColumnType("text") + .HasColumnName("revenue_account_h_path"); + + b.Property("StatisticAccountHPath") + .HasColumnType("text") + .HasColumnName("statistic_account_h_path"); + + b.ToTable("strategic_opportunity_gl"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_gl_strategic_opportunity_opportunity_"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260317201612_SeparateStrategicOpportunityTypes.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260317201612_SeparateStrategicOpportunityTypes.cs new file mode 100644 index 0000000..b226278 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260317201612_SeparateStrategicOpportunityTypes.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class SeparateStrategicOpportunityTypes : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("UPDATE strategic_opportunity SET opportunity_type = 0 WHERE opportunity_type = 12;"); + migrationBuilder.Sql("UPDATE strategic_opportunity SET opportunity_type = 1 WHERE opportunity_type = 13;"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("UPDATE strategic_opportunity SET opportunity_type = 12 WHERE opportunity_type = 0;"); + migrationBuilder.Sql("UPDATE strategic_opportunity SET opportunity_type = 13 WHERE opportunity_type = 1;"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260326165304_AddProgressColumnsToStrategicOpportunityGLTable.Designer.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260326165304_AddProgressColumnsToStrategicOpportunityGLTable.Designer.cs new file mode 100644 index 0000000..b237e43 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260326165304_AddProgressColumnsToStrategicOpportunityGLTable.Designer.cs @@ -0,0 +1,1436 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + [Migration("20260326165304_AddProgressColumnsToStrategicOpportunityGLTable")] + partial class AddProgressColumnsToStrategicOpportunityGLTable + { + /// + 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGuid") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedGoal") + .HasColumnType("double precision") + .HasColumnName("estimated_goal"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGuid") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LastRunTrackingDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_run_tracking_date"); + + b.Property("LastRunTrackingStatus") + .HasColumnType("smallint") + .HasColumnName("last_run_tracking_status"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGuid") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TotalImprovement") + .HasColumnType("double precision") + .HasColumnName("total_improvement"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("AverageValue") + .HasColumnType("double precision") + .HasColumnName("average_value"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("MeasureGroup") + .HasColumnType("smallint") + .HasColumnName("measure_group"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.Property("ProgressToDateActual") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_actual"); + + b.Property("ProgressToDateCommitted") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_committed"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("AccountType") + .HasColumnType("smallint") + .HasColumnName("account_type"); + + b.Property("DepartmentHPath") + .HasColumnType("text") + .HasColumnName("department_h_path"); + + b.Property("ExpenseAccountHPath") + .HasColumnType("text") + .HasColumnName("expense_account_h_path"); + + b.Property("HasStatistic") + .HasColumnType("boolean") + .HasColumnName("has_statistic"); + + b.Property("ProgressToDateActual") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_actual"); + + b.Property("ProgressToDateCommitted") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_committed"); + + b.Property("RevenueAccountHPath") + .HasColumnType("text") + .HasColumnName("revenue_account_h_path"); + + b.Property("StatisticAccountHPath") + .HasColumnType("text") + .HasColumnName("statistic_account_h_path"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_gl"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_gl_strategic_opportunity_opportunity_"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/20260326165304_AddProgressColumnsToStrategicOpportunityGLTable.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/20260326165304_AddProgressColumnsToStrategicOpportunityGLTable.cs new file mode 100644 index 0000000..7937cf7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/20260326165304_AddProgressColumnsToStrategicOpportunityGLTable.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + /// + public partial class AddProgressColumnsToStrategicOpportunityGLTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "total_committed_cost", + table: "strategic_opportunity_gl", + type: "double precision", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "progress_to_date_committed", + table: "strategic_opportunity_gl", + type: "double precision", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "progress_to_date_actual", + table: "strategic_opportunity_gl", + type: "double precision", + nullable: false, + defaultValue: 0.0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "total_committed_cost", + table: "strategic_opportunity_gl"); + + migrationBuilder.DropColumn( + name: "progress_to_date_committed", + table: "strategic_opportunity_gl"); + + migrationBuilder.DropColumn( + name: "progress_to_date_actual", + table: "strategic_opportunity_gl"); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Migrations/CentralDbContextModelSnapshot.cs b/src/Strata.ContinuousImprovement.Biz/Migrations/CentralDbContextModelSnapshot.cs new file mode 100644 index 0000000..399c024 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Migrations/CentralDbContextModelSnapshot.cs @@ -0,0 +1,1433 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Strata.ContinuousImprovement.Biz.DbContexts; + +#nullable disable + +namespace Strata.ContinuousImprovement.Biz.Migrations +{ + [DbContext(typeof(CentralDbContext))] + partial class CentralDbContextModelSnapshot : 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.ContinuousImprovement.Biz.ClientWarehouses.ClientWarehouse", b => + { + b.Property("ClientWarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_warehouse_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientWarehouseId")); + + b.Property("ClientName") + .HasColumnType("text") + .HasColumnName("client_name"); + + b.Property("DatabaseGuid") + .HasColumnType("uuid") + .HasColumnName("database_guid"); + + b.Property("WarehouseSize") + .HasColumnType("integer") + .HasColumnName("warehouse_size"); + + b.HasKey("ClientWarehouseId") + .HasName("pk_client_warehouses"); + + b.HasIndex("DatabaseGuid") + .IsUnique() + .HasDatabaseName("ix_client_warehouses_database_guid"); + + b.ToTable("client_warehouses", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierDimension", b => + { + b.Property("DeviceIdentifier") + .HasColumnType("text") + .HasColumnName("device_identifier"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("GMDNAnatomicalSpecialty1") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty1"); + + b.Property("GMDNAnatomicalSpecialty2") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty2"); + + b.Property("GMDNAnatomicalSpecialty3") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty3"); + + b.Property("GMDNAnatomicalSpecialty4") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty4"); + + b.Property("GMDNAnatomicalSpecialty5") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty5"); + + b.Property("GMDNAnatomicalSpecialty6") + .HasColumnType("text") + .HasColumnName("gmdn_anatomical_specialty6"); + + b.Property("GMDNDeviceFunction1") + .HasColumnType("text") + .HasColumnName("gmdn_device_function1"); + + b.Property("GMDNDeviceFunction2") + .HasColumnType("text") + .HasColumnName("gmdn_device_function2"); + + b.Property("GMDNDeviceFunction3") + .HasColumnType("text") + .HasColumnName("gmdn_device_function3"); + + b.Property("GMDNDeviceFunction4") + .HasColumnType("text") + .HasColumnName("gmdn_device_function4"); + + b.Property("GMDNDeviceFunction5") + .HasColumnType("text") + .HasColumnName("gmdn_device_function5"); + + b.Property("GMDNDeviceFunction6") + .HasColumnType("text") + .HasColumnName("gmdn_device_function6"); + + b.Property("GMDNDeviceFunction7") + .HasColumnType("text") + .HasColumnName("gmdn_device_function7"); + + b.Property("GMDNFeaturedAttributes1") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes1"); + + b.Property("GMDNFeaturedAttributes2") + .HasColumnType("text") + .HasColumnName("gmdn_featured_attributes2"); + + b.Property("GMDNInvasiveness1") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness1"); + + b.Property("GMDNInvasiveness2") + .HasColumnType("text") + .HasColumnName("gmdn_invasiveness2"); + + b.Property("GMDNMaterials1") + .HasColumnType("text") + .HasColumnName("gmdn_materials1"); + + b.Property("GMDNMaterials2") + .HasColumnType("text") + .HasColumnName("gmdn_materials2"); + + b.Property("GMDNNamesIndex1") + .HasColumnType("text") + .HasColumnName("gmdn_names_index1"); + + b.Property("GMDNNamesIndex2") + .HasColumnType("text") + .HasColumnName("gmdn_names_index2"); + + b.Property("GMDNNamesIndex3") + .HasColumnType("text") + .HasColumnName("gmdn_names_index3"); + + b.Property("GMDNNamesIndex4") + .HasColumnType("text") + .HasColumnName("gmdn_names_index4"); + + b.Property("GMDNNamesIndex5") + .HasColumnType("text") + .HasColumnName("gmdn_names_index5"); + + b.Property("GMDNNamesIndex6") + .HasColumnType("text") + .HasColumnName("gmdn_names_index6"); + + b.Property("GMDNNamesIndex7") + .HasColumnType("text") + .HasColumnName("gmdn_names_index7"); + + b.Property("GMDNPower1") + .HasColumnType("text") + .HasColumnName("gmdn_power1"); + + b.Property("GMDNPower2") + .HasColumnType("text") + .HasColumnName("gmdn_power2"); + + b.Property("GMDNTermCode") + .HasColumnType("text") + .HasColumnName("gmdn_term_code"); + + b.Property("GMDNTermDefinition") + .HasColumnType("text") + .HasColumnName("gmdn_term_definition"); + + b.Property("GMDNTermName") + .HasColumnType("text") + .HasColumnName("gmdn_term_name"); + + b.Property("GMDNUseFrequency1") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency1"); + + b.Property("GMDNUseFrequency2") + .HasColumnType("text") + .HasColumnName("gmdn_use_frequency2"); + + b.Property("GUDIDCompanyName") + .HasColumnType("text") + .HasColumnName("gudid_company_name"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("DeviceIdentifier") + .HasName("pk_device_identifier_dimension"); + + b.ToTable("device_identifier_dimension", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.DeviceIdentifierTerm", b => + { + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("CompanyName") + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("RowNumber") + .HasColumnType("integer") + .HasColumnName("row_number"); + + b.HasKey("DeviceId", "PrimaryDI", "DeviceIdType"); + + b.ToTable((string)null); + + b.ToView("device_identifier_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNCategory", b => + { + b.Property("NodeId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("node_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NodeId")); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.Property("Definition") + .HasColumnType("text") + .HasColumnName("definition"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("NodeId") + .HasName("pk_gmdn_categories"); + + b.ToTable("gmdn_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTerm", b => + { + b.Property("TermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TermId")); + + b.Property("CreatedDate") + .HasColumnType("text") + .HasColumnName("created_date"); + + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("TermDefinition") + .HasColumnType("text") + .HasColumnName("term_definition"); + + b.Property("TermIsIVD") + .HasColumnType("text") + .HasColumnName("term_is_ivd"); + + b.Property("TermName") + .HasColumnType("text") + .HasColumnName("term_name"); + + b.Property("TermStatus") + .HasColumnType("text") + .HasColumnName("term_status"); + + b.HasKey("TermId") + .HasName("pk_gmdn_terms"); + + b.ToTable("gmdn_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GMDNTermCategory", b => + { + b.Property("TermCode") + .HasColumnType("text") + .HasColumnName("term_code"); + + b.Property("CategoryCode") + .HasColumnType("text") + .HasColumnName("category_code"); + + b.HasKey("TermCode", "CategoryCode") + .HasName("pk_gmdn_term_categories"); + + b.ToTable("gmdn_term_categories", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDDevice", b => + { + b.Property("PrimaryDi") + .HasMaxLength(24) + .HasColumnType("character varying(24)") + .HasColumnName("primary_di"); + + b.Property("BrandName") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("brand_name"); + + b.Property("CatalogNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("catalog_number"); + + b.Property("CompanyName") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("company_name"); + + b.Property("DeviceCombinationProduct") + .HasColumnType("boolean") + .HasColumnName("device_combination_product"); + + b.Property("DeviceCommDistributionEndDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_comm_distribution_end_date"); + + b.Property("DeviceCommDistributionStatus") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("device_comm_distribution_status"); + + b.Property("DeviceCount") + .HasColumnType("integer") + .HasColumnName("device_count"); + + b.Property("DeviceDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("device_description"); + + b.Property("DeviceHctp") + .HasColumnType("boolean") + .HasColumnName("device_hctp"); + + b.Property("DeviceKit") + .HasColumnType("boolean") + .HasColumnName("device_kit"); + + b.Property("DevicePublishDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("device_publish_date"); + + b.Property("DeviceRecordStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("device_record_status"); + + b.Property("DeviceSterile") + .HasColumnType("boolean") + .HasColumnName("device_sterile"); + + b.Property("DmExempt") + .HasColumnType("boolean") + .HasColumnName("dm_exempt"); + + b.Property("DonationIdNumber") + .HasColumnType("boolean") + .HasColumnName("donation_id_number"); + + b.Property("DunsNumber") + .HasMaxLength(16) + .HasColumnType("character varying(16)") + .HasColumnName("duns_number"); + + b.Property("ExpirationDate") + .HasColumnType("boolean") + .HasColumnName("expiration_date"); + + b.Property("LabeledContainsNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_contains_nrl"); + + b.Property("LabeledNoNrl") + .HasColumnType("boolean") + .HasColumnName("labeled_no_nrl"); + + b.Property("LotBatch") + .HasColumnType("boolean") + .HasColumnName("lot_batch"); + + b.Property("ManufacturingDate") + .HasColumnType("boolean") + .HasColumnName("manufacturing_date"); + + b.Property("MriSafetyStatus") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("mri_safety_status"); + + b.Property("Otc") + .HasColumnType("boolean") + .HasColumnName("otc"); + + b.Property("PremarketExempt") + .HasColumnType("boolean") + .HasColumnName("premarket_exempt"); + + b.Property("PublicDeviceRecordKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("public_device_record_key"); + + b.Property("PublicVersionDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("public_version_date"); + + b.Property("PublicVersionNumber") + .HasColumnType("integer") + .HasColumnName("public_version_number"); + + b.Property("PublicVersionStatus") + .HasMaxLength(12) + .HasColumnType("character varying(12)") + .HasColumnName("public_version_status"); + + b.Property("Rx") + .HasColumnType("boolean") + .HasColumnName("rx"); + + b.Property("SerialNumber") + .HasColumnType("boolean") + .HasColumnName("serial_number"); + + b.Property("SingleUse") + .HasColumnType("boolean") + .HasColumnName("single_use"); + + b.Property("SterilizationPriorToUse") + .HasColumnType("boolean") + .HasColumnName("sterilization_prior_to_use"); + + b.Property("VersionModelNumber") + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasColumnName("version_model_number"); + + b.HasKey("PrimaryDi") + .HasName("pk_gudid_devices"); + + b.HasIndex("PrimaryDi") + .HasDatabaseName("ix_gudid_devices_primary_di"); + + b.HasIndex("PublicVersionDate") + .HasDatabaseName("ix_gudid_devices_public_version_date"); + + b.ToTable("gudid_devices", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDIdentifier", b => + { + b.Property("GudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId") + .HasDatabaseName("ix_gudid_identifiers_device_id"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI") + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type"); + + b.ToTable("gudid_identifiers", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.GUDIDTerm", b => + { + b.Property("GUDIDTermId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("gudid_term_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("GUDIDTermId")); + + b.Property("GMDNCode") + .HasColumnType("text") + .HasColumnName("gmdn_code"); + + b.Property("GMDNCodeStatus") + .HasColumnType("integer") + .HasColumnName("gmdn_code_status"); + + b.Property("GMDNPTDefinition") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("gmdnpt_definition"); + + b.Property("GMDNPTName") + .HasMaxLength(150) + .HasColumnType("character varying(150)") + .HasColumnName("gmdnpt_name"); + + b.Property("Implantable") + .HasColumnType("boolean") + .HasColumnName("implantable"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("GUDIDTermId") + .HasName("pk_gudid_terms"); + + b.HasIndex("PrimaryDI") + .HasDatabaseName("ix_gudid_terms_primary_di"); + + b.ToTable("gudid_terms", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.GMDN.TmpGUDIDIdentifier", b => + { + b.Property("TmpGudidIdentifierId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("tmp_gudid_identifier_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("TmpGudidIdentifierId")); + + b.Property("ContainsDINumber") + .HasColumnType("text") + .HasColumnName("contains_di_number"); + + b.Property("DeviceId") + .HasColumnType("text") + .HasColumnName("device_id"); + + b.Property("DeviceIdIssuingAgency") + .HasColumnType("text") + .HasColumnName("device_id_issuing_agency"); + + b.Property("DeviceIdType") + .HasColumnType("text") + .HasColumnName("device_id_type"); + + b.Property("PkgDiscontinueDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("pkg_discontinue_date"); + + b.Property("PkgQuantity") + .HasColumnType("integer") + .HasColumnName("pkg_quantity"); + + b.Property("PkgStatus") + .HasColumnType("text") + .HasColumnName("pkg_status"); + + b.Property("PkgType") + .HasColumnType("text") + .HasColumnName("pkg_type"); + + b.Property("PrimaryDI") + .HasColumnType("text") + .HasColumnName("primary_di"); + + b.HasKey("TmpGudidIdentifierId") + .HasName("pk_gudid_identifiers"); + + b.HasIndex("DeviceId", "PrimaryDI", "DeviceIdType") + .IsUnique() + .HasDatabaseName("ix_gudid_identifiers_device_id_primary_di_device_id_type1"); + + b.ToTable("gudid_identifiers", "tmp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("NetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + b.Property("AnalyticsWorkbookId") + .HasColumnType("integer") + .HasColumnName("analytics_workbook_id"); + + b.Property("IdentifiedSavings") + .HasColumnType("integer") + .HasColumnName("identified_savings"); + + b.Property("IdentifiedSavingsDescription") + .HasColumnType("text") + .HasColumnName("identified_savings_description"); + + b.Property("InitiativeStatus") + .HasColumnType("integer") + .HasColumnName("initiative_status"); + + b.Property("IsPublished") + .HasColumnType("boolean") + .HasColumnName("is_published"); + + b.HasKey("StrataId", "NetworkOpportunityId") + .HasName("pk_client_network_opportunities"); + + b.HasIndex("NetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunities_network_opportunity_id"); + + b.ToTable("client_network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.Property("ClientNetworkOpportunityLinkId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_link_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ClientNetworkOpportunityLinkId")); + + b.Property("ClientNetworkOpportunityNetworkOpportunityId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_network_opportunity_id"); + + b.Property("ClientNetworkOpportunityStrataId") + .HasColumnType("integer") + .HasColumnName("client_network_opportunity_strata_id"); + + b.Property("ConfigurationGuid") + .HasColumnType("uuid") + .HasColumnName("configuration_guid"); + + b.Property("InitiativeGuid") + .HasColumnType("uuid") + .HasColumnName("initiative_guid"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.HasKey("ClientNetworkOpportunityLinkId") + .HasName("pk_client_network_opportunity_links"); + + b.HasIndex("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasDatabaseName("ix_client_network_opportunity_links_client_network_opportunity"); + + b.ToTable("client_network_opportunity_links", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Property("NetworkOpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("network_opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("NetworkOpportunityId")); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkbookTemplateId") + .HasColumnType("integer") + .HasColumnName("workbook_template_id"); + + b.HasKey("NetworkOpportunityId") + .HasName("pk_network_opportunities"); + + b.ToTable("network_opportunities", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityComments.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("comment_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("CommentId")); + + b.Property("Author") + .HasColumnType("text") + .HasColumnName("author"); + + b.Property("CommentDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("comment_date"); + + b.Property("CommentText") + .HasColumnType("text") + .HasColumnName("comment_text"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("SourceOfSavingsId") + .HasColumnType("integer") + .HasColumnName("source_of_savings_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("CommentId") + .HasName("pk_comments"); + + b.ToTable("comments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment.OpportunityRoleAssignment", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("IdentityGuid") + .HasColumnType("uuid") + .HasColumnName("identity_guid"); + + b.Property("IdentityName") + .HasColumnType("text") + .HasColumnName("identity_name"); + + b.Property("IsRead") + .HasColumnType("boolean") + .HasColumnName("is_read"); + + b.Property("IsSecureWrite") + .HasColumnType("boolean") + .HasColumnName("is_secure_write"); + + b.Property("IsWrite") + .HasColumnType("boolean") + .HasColumnName("is_write"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "IdentityGuid") + .HasName("pk_opportunity_role_assignments"); + + b.ToTable("opportunity_role_assignments", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks.OpportunityTypeWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityType") + .HasColumnType("integer") + .HasColumnName("opportunity_type"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityType", "WorkbookId") + .HasName("pk_opportunity_type_workbooks"); + + b.ToTable("opportunity_type_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.OpportunityWorkbooks.OpportunityWorkbook", b => + { + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("OpportunityGuid") + .HasColumnType("uuid") + .HasColumnName("opportunity_guid"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.HasKey("StrataId", "OpportunityGuid", "WorkbookId") + .HasName("pk_opportunity_workbooks"); + + b.ToTable("opportunity_workbooks", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("IsExcluded") + .HasColumnType("boolean") + .HasColumnName("is_excluded"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_inclusion_exclusions"); + + b.ToTable("exploration_inclusion_exclusions", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Property("ExplorationPopulationId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ExplorationPopulationId")); + + b.Property("CaseTypeFamilyId") + .HasColumnType("integer") + .HasColumnName("case_type_family_id"); + + b.Property("CodeType") + .HasColumnType("smallint") + .HasColumnName("code_type"); + + b.Property("CostDriver") + .HasColumnType("text") + .HasColumnName("cost_driver"); + + b.Property("CostExclusionType") + .HasColumnType("smallint") + .HasColumnName("cost_exclusion_type"); + + b.Property("CostExclusionValue") + .HasColumnType("numeric") + .HasColumnName("cost_exclusion_value"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("LosExclusionType") + .HasColumnType("smallint") + .HasColumnName("los_exclusion_type"); + + b.Property("LosExclusionValue") + .HasColumnType("numeric") + .HasColumnName("los_exclusion_value"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TotalEncounters") + .HasColumnType("integer") + .HasColumnName("total_encounters"); + + b.Property("TotalVariation") + .HasColumnType("numeric") + .HasColumnName("total_variation"); + + b.HasKey("ExplorationPopulationId") + .HasName("pk_exploration_populations"); + + b.ToTable("exploration_populations", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("EncounterId") + .HasColumnType("bigint") + .HasColumnName("encounter_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("ExplorationPopulationId", "EncounterId") + .HasName("pk_exploration_population_encounters"); + + b.ToTable("exploration_population_encounters", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Property("OpportunityId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OpportunityId")); + + b.Property("BaselineDistribution") + .HasColumnType("smallint") + .HasColumnName("baseline_distribution"); + + b.Property("BaselineEndDate") + .HasColumnType("date") + .HasColumnName("baseline_end_date"); + + b.Property("BaselineStartDate") + .HasColumnType("date") + .HasColumnName("baseline_start_date"); + + b.Property("BaselineType") + .HasColumnType("smallint") + .HasColumnName("baseline_type"); + + b.Property("CostLeaderGuid") + .HasColumnType("uuid") + .HasColumnName("cost_leader_guid"); + + b.Property("EstimatedGoal") + .HasColumnType("double precision") + .HasColumnName("estimated_goal"); + + b.Property("EstimatedTrackingDuration") + .HasColumnType("smallint") + .HasColumnName("estimated_tracking_duration"); + + b.Property("ExecutiveSponsorGuid") + .HasColumnType("uuid") + .HasColumnName("executive_sponsor_guid"); + + b.Property("LastRunTrackingDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_run_tracking_date"); + + b.Property("LastRunTrackingStatus") + .HasColumnType("smallint") + .HasColumnName("last_run_tracking_status"); + + b.Property("LayoutJSON") + .HasColumnType("text") + .HasColumnName("layout_json"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OpportunityFiltersJSON") + .HasColumnType("text") + .HasColumnName("opportunity_filters_json"); + + b.Property("OpportunityType") + .HasColumnType("smallint") + .HasColumnName("opportunity_type"); + + b.Property("OwnerGuid") + .HasColumnType("uuid") + .HasColumnName("owner_guid"); + + b.Property("Rollup1Id") + .HasColumnType("integer") + .HasColumnName("rollup1id"); + + b.Property("Rollup2Id") + .HasColumnType("integer") + .HasColumnName("rollup2id"); + + b.Property("Rollup3Id") + .HasColumnType("integer") + .HasColumnName("rollup3id"); + + b.Property("Status") + .HasColumnType("smallint") + .HasColumnName("status"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.Property("TeamMembers") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)") + .HasColumnName("team_members"); + + b.Property("TotalImprovement") + .HasColumnType("double precision") + .HasColumnName("total_improvement"); + + b.Property("TrackingEndDate") + .HasColumnType("date") + .HasColumnName("tracking_end_date"); + + b.Property("TrackingStartDate") + .HasColumnType("date") + .HasColumnName("tracking_start_date"); + + b.HasKey("OpportunityId") + .HasName("pk_strategic_opportunity"); + + b.ToTable("strategic_opportunity"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.Property("MeasureId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("measure_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("MeasureId")); + + b.Property("AcceptableThreshold") + .HasColumnType("real") + .HasColumnName("acceptable_threshold"); + + b.Property("AverageValue") + .HasColumnType("double precision") + .HasColumnName("average_value"); + + b.Property("DesiredResult") + .HasColumnType("smallint") + .HasColumnName("desired_result"); + + b.Property("FiltersJSON") + .HasColumnType("text") + .HasColumnName("filters_json"); + + b.Property("MeasureGroup") + .HasColumnType("smallint") + .HasColumnName("measure_group"); + + b.Property("MeasureName") + .HasColumnType("text") + .HasColumnName("measure_name"); + + b.Property("MeasureType") + .HasColumnType("smallint") + .HasColumnName("measure_type"); + + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("MeasureId") + .HasName("pk_strategic_opportunity_measure"); + + b.HasIndex("OpportunityId") + .HasDatabaseName("ix_strategic_opportunity_measure_opportunity_id"); + + b.ToTable("strategic_opportunity_measure", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("MonthNumber") + .HasColumnType("smallint") + .HasColumnName("month_number"); + + b.Property("Percentage") + .HasColumnType("double precision") + .HasColumnName("percentage"); + + b.HasKey("OpportunityId", "MonthNumber") + .HasName("pk_strategic_opportunity_ramp_up"); + + b.ToTable("strategic_opportunity_ramp_up", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.Property("OpportunityId") + .HasColumnType("bigint") + .HasColumnName("opportunity_id"); + + b.Property("WorkbookId") + .HasColumnType("integer") + .HasColumnName("workbook_id"); + + b.Property("StrataId") + .HasColumnType("integer") + .HasColumnName("strata_id"); + + b.HasKey("OpportunityId", "WorkbookId") + .HasName("pk_strategic_opportunity_workbook"); + + b.ToTable("strategic_opportunity_workbook", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.Property("StrategicOpportunityId") + .HasColumnType("bigint") + .HasColumnName("strategic_opportunity_id"); + + b.Property("UserGuid") + .HasColumnType("uuid") + .HasColumnName("user_guid"); + + b.HasKey("StrategicOpportunityId", "UserGuid") + .HasName("pk_strategic_stakeholders_link"); + + b.ToTable("strategic_stakeholders_link", (string)null); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("ChargeCodeHPath") + .HasColumnType("text") + .HasColumnName("charge_code_h_path"); + + b.Property("ExplorationPopulationId") + .HasColumnType("bigint") + .HasColumnName("exploration_population_id"); + + b.Property("PatientPopulationHPath") + .HasColumnType("text") + .HasColumnName("patient_population_h_path"); + + b.Property("ProgressToDateActual") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_actual"); + + b.Property("ProgressToDateCommitted") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_committed"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_charge_code"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", b => + { + b.HasBaseType("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity"); + + b.Property("AccountType") + .HasColumnType("smallint") + .HasColumnName("account_type"); + + b.Property("DepartmentHPath") + .HasColumnType("text") + .HasColumnName("department_h_path"); + + b.Property("ExpenseAccountHPath") + .HasColumnType("text") + .HasColumnName("expense_account_h_path"); + + b.Property("HasStatistic") + .HasColumnType("boolean") + .HasColumnName("has_statistic"); + + b.Property("ProgressToDateActual") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_actual"); + + b.Property("ProgressToDateCommitted") + .HasColumnType("double precision") + .HasColumnName("progress_to_date_committed"); + + b.Property("RevenueAccountHPath") + .HasColumnType("text") + .HasColumnName("revenue_account_h_path"); + + b.Property("StatisticAccountHPath") + .HasColumnType("text") + .HasColumnName("statistic_account_h_path"); + + b.Property("TotalCommittedCost") + .HasColumnType("double precision") + .HasColumnName("total_committed_cost"); + + b.ToTable("strategic_opportunity_gl"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", "NetworkOpportunity") + .WithMany("ClientNetworkOpportunities") + .HasForeignKey("NetworkOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_network_opportunities_network_opportunities_network_"); + + b.Navigation("NetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunityLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", "ClientNetworkOpportunity") + .WithMany("Opportunities") + .HasForeignKey("ClientNetworkOpportunityStrataId", "ClientNetworkOpportunityNetworkOpportunityId") + .HasConstraintName("fk_client_network_opportunity_links_client_network_opportuniti"); + + b.Navigation("ClientNetworkOpportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationInclusionExclusion", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationInclusionExclusions") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_inclusion_exclusions_exploration_populations_ex"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulationEncounter", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", "ExplorationPopulation") + .WithMany("ExplorationPopulationEncounters") + .HasForeignKey("ExplorationPopulationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_exploration_population_encounters_exploration_populations_e"); + + b.Navigation("ExplorationPopulation"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityMeasure", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Measures") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_measure_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityRampUp", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("RampUps") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_ramp_up_strategic_opportunity_opportu"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityWorkbook", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Workbooks") + .HasForeignKey("OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_workbook_strategic_opportunity_opport"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicStakeholdersLink", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", "Opportunity") + .WithMany("Stakeholders") + .HasForeignKey("StrategicOpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_stakeholders_link_strategic_opportunity_strategic"); + + b.Navigation("Opportunity"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityChargeCode", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_charge_code_strategic_opportunity_opp"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", b => + { + b.HasOne("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", null) + .WithOne() + .HasForeignKey("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunityGL", "OpportunityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_strategic_opportunity_gl_strategic_opportunity_opportunity_"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.ClientNetworkOpportunity", b => + { + b.Navigation("Opportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Network.NetworkOpportunity", b => + { + b.Navigation("ClientNetworkOpportunities"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.Population.ExplorationPopulation", b => + { + b.Navigation("ExplorationInclusionExclusions"); + + b.Navigation("ExplorationPopulationEncounters"); + }); + + modelBuilder.Entity("Strata.ContinuousImprovement.Biz.StrategicOpportunities.StrategicOpportunity", b => + { + b.Navigation("Measures"); + + b.Navigation("RampUps"); + + b.Navigation("Stakeholders"); + + b.Navigation("Workbooks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunity.cs new file mode 100644 index 0000000..ca02136 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunity.cs @@ -0,0 +1,17 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Network; + +public class ClientNetworkOpportunity +{ + public int StrataId { get; set; } + public int IdentifiedSavings { get; set; } + public string IdentifiedSavingsDescription { get; set; } + public int AnalyticsWorkbookId { get; set; } + public int NetworkOpportunityId { get; set; } + public bool IsPublished { get; set; } + public InitiativeStatuses InitiativeStatus { get; set; } = InitiativeStatuses.OnHold; + public virtual NetworkOpportunity NetworkOpportunity { get; set; } + public virtual IEnumerable Opportunities { get; set; } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLink.cs b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLink.cs new file mode 100644 index 0000000..02dd88a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLink.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Network; + +public class ClientNetworkOpportunityLink +{ + public int ClientNetworkOpportunityLinkId { get; set; } + public Guid ConfigurationGuid { get; set; } + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + [NotMapped] + public string Opportunity { get; set; } + [NotMapped] + public string CostDriver { get; set; } + [NotMapped] + public string Initiative { get; set; } + + public virtual ClientNetworkOpportunity ClientNetworkOpportunity { get; set; } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLinkExistsException.cs b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLinkExistsException.cs new file mode 100644 index 0000000..f458117 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLinkExistsException.cs @@ -0,0 +1,20 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public class ClientNetworkOpportunityLinkExistsException : Exception + { + public ClientNetworkOpportunityLink ClientNetworkOpportunityLink; + + public ClientNetworkOpportunityLinkExistsException() : base() { } + + public ClientNetworkOpportunityLinkExistsException(string? message) : base(message) { } + + public ClientNetworkOpportunityLinkExistsException(ClientNetworkOpportunityLink clientNetworkOpportunityLink) + : base() + { + ClientNetworkOpportunityLink = clientNetworkOpportunityLink; + } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLinkNotFoundException.cs b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLinkNotFoundException.cs new file mode 100644 index 0000000..da00850 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLinkNotFoundException.cs @@ -0,0 +1,25 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public class ClientNetworkOpportunityLinkNotFoundException : Exception + { + + public int StrataId { get; set; } + public Guid ConfigurationGuid { get; set; } + public Guid OpportunityGuid { get; set; } + + public ClientNetworkOpportunityLinkNotFoundException() : base() { } + + public ClientNetworkOpportunityLinkNotFoundException(string? message) : base(message) { } + + public ClientNetworkOpportunityLinkNotFoundException(int strataId, Guid configurationGuid, Guid opportunityGuid) + { + StrataId = strataId; + ConfigurationGuid = configurationGuid; + OpportunityGuid = opportunityGuid; + } + + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLinkService.cs b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLinkService.cs new file mode 100644 index 0000000..62f62b4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityLinkService.cs @@ -0,0 +1,214 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Id.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public class ClientNetworkOpportunityLinkService : IClientNetworkOpportunityLinkService + { + private readonly CentralDbContext _centralDbContext; + private readonly IAllOpportunityService _allOpportunityService; + private readonly IInitiativeService _initiativeService; + private readonly IIdServiceClient _idServiceClient; + private readonly ILogger _logger; + + public ClientNetworkOpportunityLinkService(CentralDbContext centralDbContext, + IAllOpportunityService allOpportunityService, + IInitiativeService initiativeService, + IIdServiceClient idServiceClient, + ILogger logger) + { + _centralDbContext = centralDbContext; + _allOpportunityService = allOpportunityService; + _initiativeService = initiativeService; + _idServiceClient = idServiceClient; + _logger = logger; + } + + public async Task AddOpportunityLinkAsync(Guid databaseGuid, int networkOpportunityId, Guid opportunityGuid, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities) + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(p => p.NetworkOpportunityId == networkOpportunityId && p.StrataId == strataId, cancellationToken); + + var opportunity = await _allOpportunityService.GetById(opportunityGuid, cancellationToken); + if (opportunity == null) + { + throw new KeyNotFoundException("Opportunity not found"); + } + var initiative = await _initiativeService.GetById(opportunity.InitiativeGuid, cancellationToken); + + var clientOpportunityLink = clientNetworkOpportunity.Opportunities?.FirstOrDefault(opp => opp.OpportunityGuid == opportunityGuid); + if (clientOpportunityLink != null) + { + clientOpportunityLink.InitiativeGuid = opportunity.InitiativeGuid; + _centralDbContext.Update(clientOpportunityLink); + } + else + { + clientOpportunityLink = new ClientNetworkOpportunityLink + { + ConfigurationGuid = opportunity.ConfigurationGuid, + OpportunityGuid = opportunityGuid, + InitiativeGuid = opportunity.InitiativeGuid, + ClientNetworkOpportunity = clientNetworkOpportunity + }; + clientOpportunityLink.ClientNetworkOpportunity.NetworkOpportunity = clientNetworkOpportunity.NetworkOpportunity; + await _centralDbContext.AddAsync(clientOpportunityLink, cancellationToken); + } + clientOpportunityLink.Opportunity = opportunity.Name; + clientOpportunityLink.CostDriver = opportunity.CostDriver; + clientOpportunityLink.Initiative = initiative?.Name ?? ""; + clientOpportunityLink.OpportunityGuid = opportunityGuid; + clientOpportunityLink.InitiativeGuid = initiative?.InitiativeGUID ?? Guid.Empty; + await _centralDbContext.SaveChangesAsync(); + return clientOpportunityLink; + } + + public async Task> RemoveDeletedOpportunityLinks(List opportunityLinks, CancellationToken cancellationToken) + { + if (opportunityLinks.IsEmpty()) { return opportunityLinks; } + + var opportunities = (await _allOpportunityService.GetByIds(opportunityLinks.Select(ol => ol.OpportunityGuid), cancellationToken)) + .ToDictionary(key => key.OpportunityGuid, value => value.InitiativeGuid); + var initiatives = opportunities.Where(o => o.Value != Guid.Empty).Select(o => o.Value); + var deletedLinks = opportunityLinks.Where(ol => !opportunities.Keys.Contains(ol.OpportunityGuid) + || (ol.InitiativeGuid != Guid.Empty && !initiatives.Contains(ol.InitiativeGuid))); + _centralDbContext.ClientNetworkOpportunityLinks.RemoveRange(deletedLinks); + await _centralDbContext.SaveChangesAsync(); + + opportunityLinks.RemoveAll(ol => !opportunities.Keys.Contains(ol.OpportunityGuid)); + opportunityLinks.RemoveAll(ol => ol.InitiativeGuid != Guid.Empty && !initiatives.Contains(ol.InitiativeGuid)); + + return opportunityLinks; + } + + public async Task ContainsOpportunityLinkAsync(Guid databaseGuid, Guid configurationGuid, Guid opportunityGuid, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + ClientNetworkOpportunityLink clientNetworkOpportunityLink = await _centralDbContext.ClientNetworkOpportunityLinks + .Include(cno => cno.ClientNetworkOpportunity) + .FirstOrDefaultAsync(cnol => cnol.ClientNetworkOpportunity.StrataId == strataId + && cnol.ConfigurationGuid == configurationGuid + && cnol.OpportunityGuid == opportunityGuid, cancellationToken); + return clientNetworkOpportunityLink != null; + } + + public async Task ContainsInitiativeLinkAsync(Guid databaseGuid, Guid configurationGuid, Guid initiativeGuid, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + var clientNetworkOpportunityLink = await _centralDbContext.ClientNetworkOpportunityLinks + .Include(cno => cno.ClientNetworkOpportunity).Where(cno => cno.ClientNetworkOpportunity.StrataId == strataId) + .FirstOrDefaultAsync(cnol => cnol.ConfigurationGuid == configurationGuid && cnol.InitiativeGuid == initiativeGuid, cancellationToken); + return clientNetworkOpportunityLink != null; + } + + public async Task DeleteOpportunityLinkAsync(Guid databaseGuid, Guid configurationGuid, Guid opportunityGuid, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + var clientOpportunityLinks = await _centralDbContext.ClientNetworkOpportunityLinks + .Where(opp => opp.ClientNetworkOpportunity.StrataId == strataId + && opp.ConfigurationGuid == configurationGuid + && opp.OpportunityGuid == opportunityGuid) + .ToListAsync(); + if (!clientOpportunityLinks.Any()) { return; } + _centralDbContext.RemoveRange(clientOpportunityLinks); + await _centralDbContext.SaveChangesAsync(); + } + + public async Task DeleteInitiativeLinkAsync(Guid databaseGuid, Guid configurationGuid, Guid initiativeGuid, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + var clientOpportunityLinks = await _centralDbContext.ClientNetworkOpportunityLinks + .Where(opp => opp.ClientNetworkOpportunity.StrataId == strataId + && opp.ConfigurationGuid == configurationGuid + && opp.InitiativeGuid == initiativeGuid) + .ToListAsync(); + if (!clientOpportunityLinks.Any()) { return; } + foreach (var link in clientOpportunityLinks) + { + link.InitiativeGuid = Guid.Empty; + } + _centralDbContext.ClientNetworkOpportunityLinks.UpdateRange(clientOpportunityLinks); + await _centralDbContext.SaveChangesAsync(); + } + + public async Task RolloverOpportunityLinkAsync(Guid databaseGuid, + Guid sourceConfigurationGuid, Guid sourceOpportunityGuid, + Guid rolloverConfigurationGuid, Guid rolloverOpportunityGuid, Guid rolloverInitiativeGuid, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + var logInfo = new Dictionary + { + { "Rollover Opportunity", new Dictionary + { + { nameof(strataId), strataId}, + { nameof(sourceConfigurationGuid), sourceConfigurationGuid}, + { nameof(sourceOpportunityGuid), sourceOpportunityGuid}, + { nameof(rolloverConfigurationGuid), rolloverConfigurationGuid}, + { nameof(rolloverOpportunityGuid), rolloverOpportunityGuid}, + { nameof(rolloverInitiativeGuid), rolloverInitiativeGuid } + } + } + }; + _logger.LogServiceInformation(nameof(RolloverOpportunityLinkAsync) + " Started", logInfo); + + var clientOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.NetworkOpportunity) + .Include(cno => cno.Opportunities) + .FirstOrDefaultAsync(cno => cno.StrataId == strataId + && cno.Opportunities.Any(opp => opp.ConfigurationGuid == sourceConfigurationGuid && opp.OpportunityGuid == sourceOpportunityGuid)); + if (!clientOpportunity.Opportunities.Any()) { throw new ClientNetworkOpportunityLinkNotFoundException(strataId, sourceConfigurationGuid, sourceOpportunityGuid); } + + if ((await _allOpportunityService.GetById(sourceOpportunityGuid, cancellationToken)) == null) + throw new OpportunityNotFoundException(sourceOpportunityGuid); + var rolloverOpportunity = await _allOpportunityService.GetById(rolloverOpportunityGuid, cancellationToken); + if (rolloverOpportunity == null) + _logger.LogServiceError(new OpportunityNotFoundException(rolloverOpportunityGuid), info: logInfo); + + var rolloverOpportunityLink = await _centralDbContext.ClientNetworkOpportunityLinks + .Include(link => link.ClientNetworkOpportunity) + .ThenInclude(cno => cno.NetworkOpportunity) + .FirstOrDefaultAsync(link => + link.ClientNetworkOpportunity.StrataId == strataId && + link.ConfigurationGuid == rolloverConfigurationGuid && + link.OpportunityGuid == rolloverOpportunityGuid, cancellationToken); + if (rolloverOpportunityLink != null) { throw new ClientNetworkOpportunityLinkExistsException(rolloverOpportunityLink); } + + rolloverOpportunityLink = new ClientNetworkOpportunityLink + { + ConfigurationGuid = rolloverConfigurationGuid, + OpportunityGuid = rolloverOpportunityGuid, + InitiativeGuid = rolloverInitiativeGuid, + ClientNetworkOpportunity = clientOpportunity + }; + rolloverOpportunityLink.ClientNetworkOpportunity.NetworkOpportunity = clientOpportunity.NetworkOpportunity; + try + { + await _centralDbContext.AddAsync(rolloverOpportunityLink, cancellationToken); + await _centralDbContext.SaveChangesAsync(cancellationToken); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, ex.Message, logInfo); + throw; + } + _logger.LogServiceInformation(nameof(RolloverOpportunityLinkAsync) + " Finished", logInfo); + return rolloverOpportunityLink; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityService.cs new file mode 100644 index 0000000..41773d2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/ClientNetworkOpportunityService.cs @@ -0,0 +1,418 @@ +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using MoreLinq.Extensions; +using Strata.Analytics.Client; +using Strata.Analytics.Models.Templates.Publishing; +using Strata.Analytics.Models.Utils; +using Strata.Analytics.Models.Workbooks; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.All.Opportunities; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Exploration; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.OpportunityComments; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Id.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public class ClientNetworkOpportunityService : IClientNetworkOpportunityService + { + private readonly CentralDbContext _centralDbContext; + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly IAllOpportunityService _allOpportunityService; + private readonly IInitiativeService _initiativeService; + private readonly IIdServiceClient _idServiceClient; + private readonly ICommentService _commentService; + private readonly IHubContext _notificationHubContext; + private readonly ILogger _logger; + private readonly IWorkbookService _workbookService; + private readonly IAnalyticsService _analyticsService; + private readonly INetworkOpportunityService _networkOpportunityService; + + public ClientNetworkOpportunityService(CentralDbContext centralDbContext, + IAsyncDbContextFactory dbContextFactory, + IAllOpportunityService allOpportunityService, + IInitiativeService initiativeService, + IIdServiceClient idServiceClient, ICommentService commentService, + IHubContext notificationHubContext, + ILogger logger, + IWorkbookService workbookService, + IAnalyticsService analyticsService, + INetworkOpportunityService networkOpportunityService) + { + _centralDbContext = centralDbContext; + _dbContextFactory = dbContextFactory; + _allOpportunityService = allOpportunityService; + _initiativeService = initiativeService; + _idServiceClient = idServiceClient; + _commentService = commentService; + _notificationHubContext = notificationHubContext; + _logger = logger; + _workbookService = workbookService; + _analyticsService = analyticsService; + _networkOpportunityService = networkOpportunityService; + } + + private async Task GetDbContextAsync(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task ContainsAsync(Guid databaseGuid, int networkOpportunityId, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + return await _centralDbContext.ClientNetworkOpportunities + .ContainsAsync(new ClientNetworkOpportunity() { StrataId = strataId, NetworkOpportunityId = networkOpportunityId }, cancellationToken); + } + + public async Task GetByIdAsync(Guid databaseGuid, int networkOpportunityId, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + var networkOpportunity = await _centralDbContext.NetworkOpportunities + .Include(p => p.ClientNetworkOpportunities.Where(cno => cno.StrataId == strataId)) + .ThenInclude(cno => cno.Opportunities) + .SingleOrDefaultAsync(opp => opp.NetworkOpportunityId == networkOpportunityId, cancellationToken); + var opportunityLinks = networkOpportunity.ClientNetworkOpportunities.SelectMany(cno => cno.Opportunities); + foreach (var link in opportunityLinks.Where(l => !l.InitiativeGuid.Equals(Guid.Empty))) + { + var opportunity = await _allOpportunityService.GetById(link.OpportunityGuid, cancellationToken); + if (opportunity != null) + { + link.Opportunity = opportunity.Name; + link.CostDriver = opportunity.CostDriver; + } + var initiative = await _initiativeService.GetById(link.InitiativeGuid, cancellationToken); + if (initiative != null) + { + link.Initiative = initiative.Name; + } + } + + // Create client opp if it doesn't exist + var clientNetworkOpportunity = networkOpportunity.ClientNetworkOpportunities + .SingleOrDefault(opp => opp.StrataId == strataId); + + if (clientNetworkOpportunity == null) + { + clientNetworkOpportunity = new ClientNetworkOpportunity(); + clientNetworkOpportunity.NetworkOpportunityId = networkOpportunityId; + clientNetworkOpportunity.NetworkOpportunity = networkOpportunity; + clientNetworkOpportunity.StrataId = strataId; + + await _centralDbContext.ClientNetworkOpportunities.AddAsync(clientNetworkOpportunity, cancellationToken); + await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + // Create and link a workbook if not associated with one + if (clientNetworkOpportunity.AnalyticsWorkbookId == 0) + { + await CreateWorkbookFromTemplate(clientNetworkOpportunity, cancellationToken); + } + return clientNetworkOpportunity; + } + + private async Task CreateWorkbookFromTemplate(ClientNetworkOpportunity clientNetworkOpportunity, CancellationToken cancellationToken) + { + try + { + var template = await _analyticsService.GetTemplateAsync(clientNetworkOpportunity.NetworkOpportunity.WorkbookTemplateId, cancellationToken); + var createInfo = new TemplatePublishLocalInfo() + { + Description = template.Description, + Name = template.Name, + IsWorkbookHidden = true, + }; + var clientWorkbook = await _analyticsService.PublishTemplateLocalAsync(clientNetworkOpportunity.NetworkOpportunity.WorkbookTemplateId, createInfo, cancellationToken); + clientNetworkOpportunity.AnalyticsWorkbookId = clientWorkbook.WorkbookId; + + await _centralDbContext.SaveChangesAsync(cancellationToken); + } + catch (Exception ex) + { + throw new CreateWorkbookFromTemplateException(ex.Message); + } + } + + public async Task RevertWorkbookToTemplateAsync(Guid databaseGuid, ClientNetworkOpportunity clientNetworkOpportunity, CancellationToken cancellationToken) + { + try + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + clientNetworkOpportunity.AnalyticsWorkbookId = 0; + await CreateWorkbookFromTemplate(clientNetworkOpportunity, cancellationToken); + await PublishAsync(databaseGuid, clientNetworkOpportunity, false, cancellationToken); + return true; + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "Failed to revert workbook to template"); + return false; + } + } + + public async Task> GetOpportunitiesAsync(Guid databaseGuid, PagingOptions pagingOptions, NetworkOpportunityFilters filters, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + + var networkOpportunities = await _centralDbContext.NetworkOpportunities + .Include(o => o.ClientNetworkOpportunities.Where(o => o.StrataId == strataId)) + .ThenInclude(o => o.Opportunities) + .DefaultIfEmpty().ToListAsync(cancellationToken); + + var opportunitySummaries = new List(); + + networkOpportunities.ForEach(opportunity => + { + var clientNetworkOpportunity = opportunity?.ClientNetworkOpportunities.SingleOrDefault(); + + if (clientNetworkOpportunity != null && clientNetworkOpportunity.IsPublished) + { + var summary = new NetworkOpportunitySummary() + { + NetworkOpportunityId = opportunity.NetworkOpportunityId, + Name = opportunity.Name, + Description = opportunity.Description, + CostDriver = opportunity.CostDriver, + IdentifiedSavings = clientNetworkOpportunity.IdentifiedSavings, + IsPublished = clientNetworkOpportunity.IsPublished, + AnalyticsWorkbookId = clientNetworkOpportunity.AnalyticsWorkbookId, + InitiativeStatus = clientNetworkOpportunity.InitiativeStatus, + HasInitiative = clientNetworkOpportunity?.Opportunities?.Any(o => o.InitiativeGuid != Guid.Empty) ?? false + }; + opportunitySummaries.Add(summary); + } + }); + + return opportunitySummaries.ToFilteredPagedList(pagingOptions, filters.GetFilters()); + + } + + public async Task ConfigureAsync(Guid databaseGuid, int networkOpportunityId, ClientNetworkOpportunity clientOpportunity, NetworkOpportunitySaveModel networkOpportunity, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + + var existingOpportunity = await _centralDbContext.ClientNetworkOpportunities + .SingleOrDefaultAsync(p => p.NetworkOpportunityId == networkOpportunityId && p.StrataId == strataId, cancellationToken); + try + { + + if (existingOpportunity == null) + { + await AddAsync(strataId, clientOpportunity, cancellationToken); + } + else + { + await _networkOpportunityService.UpdateAsync(networkOpportunity, cancellationToken); + await UpdateAsync(existingOpportunity, clientOpportunity, cancellationToken); + } + } + catch (Exception ex) + { + + throw; + } + } + + public async Task AddAsync(int strataId, ClientNetworkOpportunity opportunity, CancellationToken cancellationToken) + { + // Can't save without configuring savings + if (opportunity.IdentifiedSavings < 1) { return opportunity; } + + var clientNetworkOpportunity = new ClientNetworkOpportunity(); + clientNetworkOpportunity.NetworkOpportunityId = opportunity.NetworkOpportunityId; + clientNetworkOpportunity.IdentifiedSavings = opportunity.IdentifiedSavings; + clientNetworkOpportunity.IsPublished = opportunity.IsPublished; + clientNetworkOpportunity.AnalyticsWorkbookId = opportunity.AnalyticsWorkbookId; + clientNetworkOpportunity.StrataId = strataId; + + await _centralDbContext.AddAsync(clientNetworkOpportunity); + await _centralDbContext.SaveChangesAsync(cancellationToken); + + await RefreshNetworkOpportunityLinkAsync(clientNetworkOpportunity, cancellationToken); + return clientNetworkOpportunity; + } + + public async Task UpdateAsync(ClientNetworkOpportunity targetOpportunity, ClientNetworkOpportunity opportunity, CancellationToken cancellationToken) + { + targetOpportunity.IdentifiedSavings = opportunity.IdentifiedSavings; + targetOpportunity.IdentifiedSavingsDescription = opportunity.IdentifiedSavingsDescription; + targetOpportunity.IsPublished = opportunity.IsPublished; + targetOpportunity.AnalyticsWorkbookId = opportunity.AnalyticsWorkbookId; + + _centralDbContext.Update(targetOpportunity); + await _centralDbContext.SaveChangesAsync(cancellationToken); + + await RefreshNetworkOpportunityLinkAsync(targetOpportunity, cancellationToken); + } + + public async Task DeleteAsync(Guid databaseGuid, int networkOpportunityId, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + var networkOpportunity = await _centralDbContext.NetworkOpportunities + .Include(p => p.ClientNetworkOpportunities) + .SingleOrDefaultAsync(opp => opp.NetworkOpportunityId == networkOpportunityId, cancellationToken); + + var clientNetworkOpportunity = networkOpportunity?.ClientNetworkOpportunities + .SingleOrDefault(opp => opp.StrataId == strataId) + ?? await _centralDbContext.ClientNetworkOpportunities + .SingleOrDefaultAsync(opp => opp.NetworkOpportunityId == networkOpportunityId && opp.StrataId == strataId, cancellationToken); + + if (clientNetworkOpportunity == null) { return; } + _centralDbContext.Remove(clientNetworkOpportunity); + await _centralDbContext.SaveChangesAsync(cancellationToken); + + var dbContext = await GetDbContextAsync(cancellationToken); + var nol = await dbContext.NetworkOpportunityLinks + .SingleOrDefaultAsync(nol => nol.OpportunityId == networkOpportunityId, cancellationToken); + if (nol != null) + { + dbContext.NetworkOpportunityLinks.Remove(nol); + await dbContext.SaveChangesAsync(cancellationToken); + } + + await _commentService.DeleteCommentsAsync(databaseGuid, 10, networkOpportunityId, cancellationToken); + } + + public async Task UpdateInitiativeStatusAsync(Guid databaseGuid, int networkOpportunityId, InitiativeStatuses status, CancellationToken cancellationToken) + { + var hubKey = databaseGuid.ToString() + networkOpportunityId; + var hub = _notificationHubContext.Clients.Group(hubKey); + + try + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .SingleOrDefaultAsync(p => p.NetworkOpportunityId == networkOpportunityId && p.StrataId == strataId, cancellationToken); + + clientNetworkOpportunity.InitiativeStatus = status; + var result = _centralDbContext.SaveChanges(); + if (result > 0) + { + await hub.SendInitiativeStatusChangeSuccess(hubKey); + } + else + { + await hub.SendInitiativeStatusChangeError(hubKey); + } + } + catch (Exception ex) + { + await hub.SendInitiativeStatusChangeError(hubKey); + } + } + + private async Task RefreshNetworkOpportunityLinkAsync(ClientNetworkOpportunity opportunity, CancellationToken cancellationToken) + { + var dbContext = await GetDbContextAsync(cancellationToken); + var isPublished = opportunity.IsPublished; + var nol = await dbContext.NetworkOpportunityLinks + .SingleOrDefaultAsync(nol => nol.OpportunityId == opportunity.NetworkOpportunityId, cancellationToken); + if (isPublished && nol == null) + { + await dbContext.NetworkOpportunityLinks.AddAsync(new NetworkOpportunityLink(opportunity), cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + } + else if (nol != null) + { + dbContext.NetworkOpportunityLinks.Remove(nol); + await dbContext.SaveChangesAsync(cancellationToken); + } + } + + public async Task PublishAsync(Guid databaseGuid, ClientNetworkOpportunity opportunity, bool isPublished, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + opportunity.IsPublished = isPublished; + + var existingOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(p => p.NetworkOpportunityId == opportunity.NetworkOpportunityId && p.StrataId == strataId, cancellationToken); + try + { + if (existingOpportunity == null) + { + existingOpportunity = await AddAsync(strataId, opportunity, cancellationToken); + } + else + { + await UpdateAsync(existingOpportunity, opportunity, cancellationToken); + } + + Batch roleAssignments = default; + if (isPublished) + { + var dbContext = await GetDbContextAsync(cancellationToken); + // Add CI basic user permissions to Workbook so they may view + roleAssignments = await _workbookService.GetDefaultCIUserRoleAssignmentsAsync(dbContext, cancellationToken); + await _analyticsService.SaveRoleAssignmentsAsync(existingOpportunity.AnalyticsWorkbookId, roleAssignments, cancellationToken); + } + else + { + var workbookRoleAssignments = await _analyticsService.GetRoleAssignmentsAsync(existingOpportunity.AnalyticsWorkbookId, cancellationToken); + roleAssignments = new Batch + { + Deletes = workbookRoleAssignments.Select(wa => wa.IdentityGuid) + }; + await _analyticsService.SaveRoleAssignmentsAsync(existingOpportunity.AnalyticsWorkbookId, roleAssignments, cancellationToken); + } + + return true; + } + catch + { + return false; + } + } + + public async Task> GetOpportunityNameByOpportunities(Guid databaseGuid, IEnumerable opportunityGuids, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + return await _centralDbContext.ClientNetworkOpportunityLinks + .Include(opp => opp.ClientNetworkOpportunity) + .ThenInclude(cno => cno.NetworkOpportunity) + .Where(opp => opp.ClientNetworkOpportunity.StrataId == strataId && opportunityGuids.Contains(opp.OpportunityGuid)) + .Select(opp => new ClientOpportunityDto + { + OpportunityGuid = opp.OpportunityGuid, + InitiativeGuid = opp.InitiativeGuid, + Name = opp.ClientNetworkOpportunity.NetworkOpportunity.Name + }).ToListAsync(); + } + + public async Task> GetOpportunityNameByInitiatives(Guid databaseGuid, IEnumerable initiativeGuids, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + return await _centralDbContext.ClientNetworkOpportunityLinks + .Include(opp => opp.ClientNetworkOpportunity) + .ThenInclude(cno => cno.NetworkOpportunity) + .Where(opp => opp.ClientNetworkOpportunity.StrataId == strataId && initiativeGuids.Contains(opp.InitiativeGuid)) + .Select(opp => new ClientOpportunityDto + { + OpportunityGuid = opp.OpportunityGuid, + InitiativeGuid = opp.InitiativeGuid, + Name = opp.ClientNetworkOpportunity.NetworkOpportunity.Name + }).ToListAsync(); + } + } + + public class ClientOpportunityDto + { + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Network/CreateWorkbookFromTemplateException.cs b/src/Strata.ContinuousImprovement.Biz/Network/CreateWorkbookFromTemplateException.cs new file mode 100644 index 0000000..28883d5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/CreateWorkbookFromTemplateException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public class CreateWorkbookFromTemplateException : Exception + { + public CreateWorkbookFromTemplateException() : base() { } + + public CreateWorkbookFromTemplateException(string? message) : base(message) { } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/Filters/ClientNetworkOpportunityMapper.cs b/src/Strata.ContinuousImprovement.Biz/Network/Filters/ClientNetworkOpportunityMapper.cs new file mode 100644 index 0000000..ea11801 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/Filters/ClientNetworkOpportunityMapper.cs @@ -0,0 +1,18 @@ +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Network.Filters +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class ClientNetworkOpportunityMapper + { + public static partial ClientNetworkOpportunity MappedtoClientOpportunity(this ClientNetworkOpportunityDto src); + public static partial NetworkOpportunitySaveModel MappedtoNetworkOpportunitySaveModel(this ClientNetworkOpportunityDto src); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/Filters/NetworkFilters.cs b/src/Strata.ContinuousImprovement.Biz/Network/Filters/NetworkFilters.cs new file mode 100644 index 0000000..d3e6383 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/Filters/NetworkFilters.cs @@ -0,0 +1,36 @@ +using Strata.ContinuousImprovement.Biz.Network; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters +{ + public class NetworkOpportunityFilters + { + public string OpportunitySearch { get; set; } + public int? IdentifiedSavings { get; set; } + public InitiativeStatuses? InitiativeStatus { get; set; } + + + public List>> GetFilters() + { + var filters = new List>>(); + + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + var searchToLower = OpportunitySearch.ToLower(); + filters.Add(m => m.Name.ToLower().Contains(searchToLower) + || (m.Description != null && m.Description.ToLower().Contains(searchToLower))); + } + + if (IdentifiedSavings is not null) + filters.Add(m => (m).IdentifiedSavings >= IdentifiedSavings); + + if (InitiativeStatus is not null) + filters.Add(m => (m).InitiativeStatus.Equals(InitiativeStatus)); + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/IClientNetworkOpportunityLinkService.cs b/src/Strata.ContinuousImprovement.Biz/Network/IClientNetworkOpportunityLinkService.cs new file mode 100644 index 0000000..b355a70 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/IClientNetworkOpportunityLinkService.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public interface IClientNetworkOpportunityLinkService + { + Task AddOpportunityLinkAsync(Guid databaseGuid, int networkOpportunityId, Guid opportunityGuid, CancellationToken cancellationToken); + Task ContainsOpportunityLinkAsync(Guid databaseGuid, Guid configurationGuid, Guid opportunityGuid, CancellationToken cancellationToken); + Task ContainsInitiativeLinkAsync(Guid databaseGuid, Guid configurationGuid, Guid initiativeGuid, CancellationToken cancellationToken); + Task DeleteOpportunityLinkAsync(Guid databaseGuid, Guid configurationGuid, Guid opportunityGuid, CancellationToken cancellationToken); + Task DeleteInitiativeLinkAsync(Guid databaseGuid, Guid configurationGuid, Guid initiativeGuid, CancellationToken cancellationToken); + Task> RemoveDeletedOpportunityLinks(List opportunityLinks, CancellationToken cancellationToken); + Task RolloverOpportunityLinkAsync(Guid databaseGuid, + Guid sourceConfigurationGuid, Guid sourceOpportunityGuid, + Guid rolloverConfigurationGuid, Guid rolloverOpportunityGuid, Guid rolloverInitiativeGuid, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Network/IClientNetworkOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Network/IClientNetworkOpportunityService.cs new file mode 100644 index 0000000..b8cfb0d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/IClientNetworkOpportunityService.cs @@ -0,0 +1,27 @@ +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public interface IClientNetworkOpportunityService + { + Task ContainsAsync(Guid databaseGuid, int networkOpportunityId, CancellationToken cancellationToken); + Task> GetOpportunitiesAsync(Guid databaseGuid, PagingOptions pagingOptions, NetworkOpportunityFilters filters, CancellationToken cancellationToken); + Task GetByIdAsync(Guid databaseGuid, int networkOpportunityId, CancellationToken cancellationToken); + Task RevertWorkbookToTemplateAsync(Guid databaseGuid, ClientNetworkOpportunity networkOpportunityId, CancellationToken cancellationToken); + Task ConfigureAsync(Guid databaseGuid, int networkOpportunityId, ClientNetworkOpportunity clientOpportunity, NetworkOpportunitySaveModel networkOpportunity, CancellationToken cancellationToken); + Task UpdateInitiativeStatusAsync(Guid databaseGuid, int networkOpportunityId, InitiativeStatuses status, CancellationToken cancellationToken); + Task AddAsync(int strataId, ClientNetworkOpportunity opportunity, CancellationToken cancellationToken); + Task UpdateAsync(ClientNetworkOpportunity targetOpportunity, ClientNetworkOpportunity opportunity, CancellationToken cancellationToken); + Task DeleteAsync(Guid databaseGuid, int networkOpportunityId, CancellationToken cancellationToken); + Task PublishAsync(Guid databaseGuid, ClientNetworkOpportunity opportunity, bool isPublished, CancellationToken cancellationToken); + Task> GetOpportunityNameByOpportunities(Guid databaseGuid, IEnumerable opportunityGuids, CancellationToken cancellationToken); + Task> GetOpportunityNameByInitiatives(Guid databaseGuid, IEnumerable initiativeGuids, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Network/INetworkOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Network/INetworkOpportunityService.cs new file mode 100644 index 0000000..6273cda --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/INetworkOpportunityService.cs @@ -0,0 +1,20 @@ +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Pagination; +using System; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public interface INetworkOpportunityService + { + Task GetEnabledAsync(Guid databaseGuid, CancellationToken cancellationToken); + Task> GetOpportunitiesAsync(ClaimsPrincipal user, PagingOptions pagingOptions, NetworkOpportunityFilters filters, CancellationToken cancellationToken); + Task CreateAsync(NetworkOpportunitySaveModel opportunity, CancellationToken cancellationToken); + Task GetbyIdAsync(int networkOpportunityid, CancellationToken cancellationToken); + Task UpdateAsync(NetworkOpportunitySaveModel model, CancellationToken cancellationToken); + Task DeleteAsync(int networkOpportunityId, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunity.cs new file mode 100644 index 0000000..3528e50 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunity.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Strata.ContinuousImprovement.Biz.Network; + +public class NetworkOpportunity +{ + [Key] + [Required] + public int NetworkOpportunityId { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string CostDriver { get; set; } + public int WorkbookTemplateId { get; set; } + + public virtual IList ClientNetworkOpportunities { get; set; } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunityLink.cs b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunityLink.cs new file mode 100644 index 0000000..6788893 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunityLink.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + [Table("NetworkOpportunityLink", Schema = "cci")] + public class NetworkOpportunityLink + { + [Key] public Guid OpportunityGuid { get; set; } + public string Name { get; set; } + public double IdentifiedSavings { get; set; } + public bool IsHidden { get; set; } + public string Note { get; set; } = String.Empty; + public long OpportunityId { get; set; } + + public NetworkOpportunityLink() + { + + } + + public NetworkOpportunityLink(ClientNetworkOpportunity clientNetworkOpportunity) + { + OpportunityGuid = Guid.NewGuid(); + Name = clientNetworkOpportunity.NetworkOpportunity.Name; + OpportunityId = clientNetworkOpportunity.NetworkOpportunityId; + IdentifiedSavings = clientNetworkOpportunity.IdentifiedSavings; + IsHidden = false; + Note = ""; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunitySaveModel.cs b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunitySaveModel.cs new file mode 100644 index 0000000..6fa52f3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunitySaveModel.cs @@ -0,0 +1,10 @@ +namespace Strata.ContinuousImprovement.Biz.Network; + +public class NetworkOpportunitySaveModel +{ + public int NetworkOpportunityId { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string CostDriver { get; set; } + public int WorkbookTemplateId { get; set; } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunitySection.cs b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunitySection.cs new file mode 100644 index 0000000..84107b0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunitySection.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public class NetworkOpportunitySection + { + public IList Creators { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunityService.cs new file mode 100644 index 0000000..02a02ae --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunityService.cs @@ -0,0 +1,152 @@ +using Microsoft.EntityFrameworkCore; +using Strata.Analytics.Client; +using Strata.ApiLib.Standard.Models; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.OpportunityComments; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.CoreLib.Claims.Extensions; +using Strata.Id.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public class NetworkOpportunityService : INetworkOpportunityService + { + private readonly CentralDbContext _centralDbContext; + private readonly IIdServiceClient _idServiceClient; + private readonly ICommentService _commentService; + private readonly IAnalyticsService _analyticsService; + + public NetworkOpportunityService(CentralDbContext centralDbContext, + IIdServiceClient idServiceClient, + ICommentService commentService, + IAnalyticsService analyticsService) + { + _centralDbContext = centralDbContext; + _idServiceClient = idServiceClient; + _commentService = commentService; + _analyticsService = analyticsService; + } + + public async Task CreateAsync(NetworkOpportunitySaveModel opportunity, CancellationToken cancellationToken) + { + var newNetworkOpportunity = new NetworkOpportunity() + { + Name = opportunity.Name, + Description = opportunity.Description, + CostDriver = opportunity.CostDriver, + WorkbookTemplateId = opportunity.WorkbookTemplateId + }; + + await _centralDbContext.NetworkOpportunities.AddAsync(newNetworkOpportunity, cancellationToken); + + await _centralDbContext.SaveChangesAsync(cancellationToken); + return newNetworkOpportunity; + } + + public async Task GetbyIdAsync(int networkOpportunityId, CancellationToken cancellationToken) + { + return await _centralDbContext.NetworkOpportunities + .SingleOrDefaultAsync(p => p.NetworkOpportunityId == networkOpportunityId, cancellationToken); + } + + public async Task GetEnabledAsync(Guid databaseGuid, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + return await _centralDbContext.ClientNetworkOpportunities.AnyAsync(no => no.StrataId == strataId && no.IsPublished); + } + + public async Task> GetOpportunitiesAsync(ClaimsPrincipal user, PagingOptions pagingOptions, NetworkOpportunityFilters filters, CancellationToken cancellationToken) + { + var databaseGuid = ClaimsPrincipalExtensions.GetDatabaseGuid(user); + var strataId = await _idServiceClient.GetStrataId(databaseGuid, cancellationToken); + + var opportunitySummaries = new List(); + if (!_centralDbContext.NetworkOpportunities?.Any() ?? true) + { + return opportunitySummaries.ToFilteredPagedList(pagingOptions, filters.GetFilters()); + } + + var networkOpportunities = await _centralDbContext.NetworkOpportunities + .Include(o => o.ClientNetworkOpportunities) + .DefaultIfEmpty().ToListAsync(cancellationToken); + + networkOpportunities?.ForEach(opportunity => + { + var clientNetworkOpportunity = + opportunity.ClientNetworkOpportunities.AsQueryable() + .SingleOrDefault(opp => opp.StrataId == strataId); + var summary = new NetworkOpportunitySummary() + { + NetworkOpportunityId = opportunity.NetworkOpportunityId, + Name = opportunity.Name, + Description = opportunity.Description, + IdentifiedSavings = clientNetworkOpportunity?.IdentifiedSavings ?? 0, + AnalyticsWorkbookId = clientNetworkOpportunity?.AnalyticsWorkbookId ?? 0, + CostDriver = opportunity.CostDriver, + IsPublished = clientNetworkOpportunity?.IsPublished ?? false, + InitiativeStatus = clientNetworkOpportunity?.InitiativeStatus ?? InitiativeStatuses.OnHold, + WorkbookTemplateId = opportunity.WorkbookTemplateId + }; + opportunitySummaries.Add(summary); + }); + + return opportunitySummaries.ToFilteredPagedList(pagingOptions, filters.GetFilters()); + } + + public async Task UpdateAsync(NetworkOpportunitySaveModel saveModel, CancellationToken cancellationToken) + { + var networkOpportunity = await GetbyIdAsync(saveModel.NetworkOpportunityId, cancellationToken); + if (networkOpportunity != null) + { + networkOpportunity.Name = saveModel.Name; + networkOpportunity.Description = saveModel.Description; + networkOpportunity.CostDriver = saveModel.CostDriver; + networkOpportunity.WorkbookTemplateId = saveModel.WorkbookTemplateId; + await _centralDbContext.SaveChangesAsync(cancellationToken); + } + } + + public async Task DeleteAsync(int networkOpportunityId, CancellationToken cancellationToken) + { + try + { + // delete network opportunity and all associated client network opps + var networkOpportunity = await _centralDbContext.NetworkOpportunities.Include(x => x.ClientNetworkOpportunities) + .SingleOrDefaultAsync(x => x.NetworkOpportunityId == networkOpportunityId); + + if (networkOpportunity == null) { return false; } + + // delete network and associated client network opps + _centralDbContext.Remove(networkOpportunity); + await _centralDbContext.SaveChangesAsync(cancellationToken); + + // delete all associated workbooks & comments + if (networkOpportunity.ClientNetworkOpportunities != null) + { + foreach (var client in networkOpportunity.ClientNetworkOpportunities) + { + await _analyticsService.DeleteWorkbookAsync(client.AnalyticsWorkbookId); + + var dbGuid = await _idServiceClient.GetDatabaseGUID(client.StrataId, cancellationToken); + await _commentService.DeleteCommentsAsync(dbGuid, (int)OpportunityTypes.Network, networkOpportunityId, cancellationToken); + } + } + + return true; + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunitySummary.cs b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunitySummary.cs new file mode 100644 index 0000000..e72adf5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/NetworkOpportunitySummary.cs @@ -0,0 +1,18 @@ +using Strata.ContinuousImprovement.Biz.Shared; + +namespace Strata.ContinuousImprovement.Biz.Network; + +public class NetworkOpportunitySummary +{ + public int NetworkOpportunityId { get; set; } + public string OpportunityKey => "NET - " + NetworkOpportunityId; + public string Name { get; set; } + public string Description { get; set; } + public int IdentifiedSavings { get; set; } + public bool IsPublished { get; set; } + public InitiativeStatuses InitiativeStatus { get; set; } + public string CostDriver { get; set; } + public int AnalyticsWorkbookId { get; set; } + public bool HasInitiative { get; set; } + public int WorkbookTemplateId { get; set; } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Network/OpportunityNotFoundException.cs b/src/Strata.ContinuousImprovement.Biz/Network/OpportunityNotFoundException.cs new file mode 100644 index 0000000..730d2d0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Network/OpportunityNotFoundException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.Network +{ + public class OpportunityNotFoundException : Exception + { + public Guid OpportunityGuid { get; set; } + public OpportunityNotFoundException(Guid opportunityGduid) : base() + { + OpportunityGuid = opportunityGduid; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Notification/INotificationHub.cs b/src/Strata.ContinuousImprovement.Biz/Notification/INotificationHub.cs new file mode 100644 index 0000000..d086d06 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Notification/INotificationHub.cs @@ -0,0 +1,38 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Notification +{ + /// + /// These are the SignalR notifications sent to the react client + /// + public interface INotificationHub + { + Task SendOpportunityCreate(string opportunityName); + Task SendInitiativeCreate(string opportunityName); + Task SendOpportunityCreateDetails(string opportunityName, double progress); + Task SendOpportunityCreateError(string opportunityName, string optionalMessage = ""); + Task SendOpportunityUpdate(string opportunityName); + Task SendOpportunityUpdateDetails(string opportunityName, double progress); + Task SendOpportunityUpdateSuccess(Guid opportunityGuid, string opportunityName); + Task SendOpportunityCreateUpdateSuccess(Guid opportunityGuid, bool isTempoPage, string opportunityName, bool isUpdate, OpportunityTypes opportunityType); + Task SendInitiativeCreateSuccess(Guid opportunityGuid, string opportunityName); + Task SendOpportunityUpdateError(string opportunityName, string optionalMessage = ""); + Task SendUploadFileStart(string userName, string filename); + Task SendUploadFileFinish(string userName, string filename); + Task SendUploadFileError(string userName, string filename); + Task SendInitiativeStatusChangeSuccess(string key); + Task SendInitiativeStatusChangeError(string key); + Task SendPatientPopulationCaseTypeCreateSuccess(string key, string count); + Task SendPatientPopulationCaseTypeAlreadyExist(string key, string count); + Task SendPatientPopulationCaseTypeCreateError(string key); + Task SendExplorationOpportunityCreateSuccess(string key, string opportunityName, string opportunityGuid); + Task SendExplorationOpportunityCreateError(string key, string opportunityName); + Task SendNetworkOpportunityPublish(string opportunityName, bool isPublished); + Task SendNetworkOpportunityPublishSuccess(string opportunityName, bool isPublished); + Task SendNetworkOpportunityPublishError(string opportunityName, bool isPublished); + Task SendStrategicInitiativeTrackerSuccess(string key, byte trackingStatus, DateTime? trackingDate); + Task SendStrategicInitiativeTrackerError(string key, byte? trackingStatus, DateTime? trackingDate); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Notification/NotificationHub.cs b/src/Strata.ContinuousImprovement.Biz/Notification/NotificationHub.cs new file mode 100644 index 0000000..1ab4927 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Notification/NotificationHub.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.SignalR; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Notification +{ + public class NotificationHub : Hub + { + public async Task Register(string topic) + { + await Groups.AddToGroupAsync(Context.ConnectionId, topic); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityComments/Comment.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityComments/Comment.cs new file mode 100644 index 0000000..307606a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityComments/Comment.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Strata.ContinuousImprovement.Biz.OpportunityComments +{ + public class Comment + { + [Key] + public int CommentId { get; set; } + public int StrataId { get; set; } + public int SourceOfSavingsId { get; set; } + public long OpportunityId { get; set; } + public string CommentText { get; set; } = string.Empty; + public DateTimeOffset CommentDate { get; set; } + public string Author { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityComments/CommentService.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityComments/CommentService.cs new file mode 100644 index 0000000..5150cc4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityComments/CommentService.cs @@ -0,0 +1,73 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.Id.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OpportunityComments +{ + public class CommentService : ICommentService + { + private readonly CentralDbContext _centralDbContext; + private readonly IIdServiceClient _idServiceClient; + + public CommentService( + CentralDbContext centralDbContext, + IIdServiceClient idServiceClient) + { + _centralDbContext = centralDbContext; + _idServiceClient = idServiceClient; + } + + public async Task> GetCommentsAsync(Guid databaseGuid, int opportunityType, long opportunityId, CancellationToken cancellationToken) + { + var strataId = databaseGuid != Guid.Empty + ? await _idServiceClient.GetStrataId(databaseGuid, cancellationToken) + : 0; + return await _centralDbContext.Comments + .Where(x => x.StrataId == strataId && x.SourceOfSavingsId == opportunityType && x.OpportunityId == opportunityId) + .OrderByDescending(x => x.CommentDate).ThenBy(x => x.Author).ToListAsync(); + } + + public async Task AddCommentAsync(Guid databaseGuid, int oppportunityType, long opportunityId, string commentText, DateTimeOffset? commentDate, string author, CancellationToken cancellationToken) + { + var strataId = databaseGuid != Guid.Empty + ? await _idServiceClient.GetStrataId(databaseGuid, cancellationToken) + : 0; + var dateOfComment = commentDate?.UtcDateTime ?? DateTimeOffset.Now.UtcDateTime; + var lastComment = await _centralDbContext.Comments + .Where(c => c.StrataId == strataId && c.SourceOfSavingsId == oppportunityType && c.OpportunityId == opportunityId && c.Author == author) + .OrderByDescending(c => c.CommentDate) + .FirstOrDefaultAsync(c => c.CommentText == commentText && c.CommentDate.Date == new DateTimeOffset(dateOfComment.Date).Date); + if (lastComment != null) return lastComment; + var comment = new Comment + { + StrataId = strataId, + SourceOfSavingsId = oppportunityType, + OpportunityId = opportunityId, + CommentDate = dateOfComment, + CommentText = commentText, + Author = author + }; + _centralDbContext.Comments.Add(comment); + await _centralDbContext.SaveChangesAsync(cancellationToken); + return comment; + } + + public async Task DeleteCommentsAsync(Guid databaseGuid, int oppportunityType, long opportunityId, CancellationToken cancellationToken) + { + var strataId = databaseGuid != Guid.Empty + ? await _idServiceClient.GetStrataId(databaseGuid, cancellationToken) + : 0; + var comments = await _centralDbContext.Comments + .Where(x => x.StrataId == strataId && x.SourceOfSavingsId == oppportunityType && x.OpportunityId == opportunityId) + .ToListAsync(cancellationToken); + if (!comments.Any()) { return; } + _centralDbContext.RemoveRange(comments); + await _centralDbContext.SaveChangesAsync(cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityComments/ICommentService.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityComments/ICommentService.cs new file mode 100644 index 0000000..3607495 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityComments/ICommentService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OpportunityComments +{ + public interface ICommentService + { + Task> GetCommentsAsync(Guid databaseGuid, int opportunityType, long opportunityId, CancellationToken cancellationToken); + Task AddCommentAsync(Guid databaseGuid, int oppportunityType, long opportunityId, string commentText, DateTimeOffset? commentDate, string author, CancellationToken cancellationToken); + Task DeleteCommentsAsync(Guid databaseGuid, int oppportunityType, long opportunityId, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/IOpportunityRoleAssignmentService.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/IOpportunityRoleAssignmentService.cs new file mode 100644 index 0000000..264c1f1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/IOpportunityRoleAssignmentService.cs @@ -0,0 +1,16 @@ +using Strata.Authorization; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment +{ + public interface IOpportunityRoleAssignmentService + { + Task AddPermissionsFromDefaultsAsync(long opportunityId, CancellationToken cancellationToken); + Task> GetRoleAssignmentsAsync(long opportunityId, CancellationToken cancellationToken); + Task GetUserRoleAssignmentsAsync(long opportunityId, CancellationToken cancellationToken); + Task SaveRoleAssignmentsAsync(long opportunityId, List permissionSettings, CancellationToken cancellationToken); + Task CopyTo(long fromOpportunityId, long toOpportunityId, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/OpportunityRoleAssignment.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/OpportunityRoleAssignment.cs new file mode 100644 index 0000000..da68c66 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/OpportunityRoleAssignment.cs @@ -0,0 +1,50 @@ +using Strata.ContinuousImprovement.Biz.Security; +using System; + +namespace Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment +{ + public class OpportunityRoleAssignment + { + public int StrataId { get; set; } + public long OpportunityId { get; set; } + public Guid IdentityGuid { get; set; } + public string IdentityName { get; set; } = string.Empty; + public bool IsRead { get; set; } + public bool IsWrite { get; set; } + public bool IsSecureWrite { get; set; } + + public OpportunityRoleAssignment() { } + public OpportunityRoleAssignment(OpportunityRoleAssignmentView roleAssignment, int strataId) + { + StrataId = strataId; + OpportunityId = roleAssignment.LootId; + IdentityGuid = roleAssignment.IdentityGuid; + IdentityName = roleAssignment.IdentityName; + IsRead = roleAssignment.CanRead; + IsWrite = roleAssignment.CanWrite; + IsSecureWrite = roleAssignment.CanSecure; + } + + public OpportunityRoleAssignment(SimplePermission permission, long opportunityId, int strataId) + { + StrataId = strataId; + OpportunityId = opportunityId; + IdentityGuid = permission.IdentityGUID; + IdentityName = permission.IdentityName; + IsRead = permission.ReadValue; + IsWrite = permission.WriteValue; + IsSecureWrite = permission.SecureValue; + } + + public OpportunityRoleAssignment(OpportunityRoleAssignment role, long opportunityId) + { + StrataId = role.StrataId; + OpportunityId = opportunityId; + IdentityGuid = role.IdentityGuid; + IdentityName = role.IdentityName; + IsRead = role.IsRead; + IsWrite = role.IsWrite; + IsSecureWrite = role.IsSecureWrite; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/OpportunityRoleAssignmentService.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/OpportunityRoleAssignmentService.cs new file mode 100644 index 0000000..658ee4e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/OpportunityRoleAssignmentService.cs @@ -0,0 +1,124 @@ +using IdentityModel; +using Microsoft.EntityFrameworkCore; +using MoreLinq; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Security; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.Id.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment +{ + public class OpportunityRoleAssignmentService : IOpportunityRoleAssignmentService + { + private readonly CentralDbContext _dbContext; + private readonly ClaimsPrincipal _user; + private readonly IIdServiceClient _idServiceClient; + private readonly ISimpleSecurityService _simpleSecurityService; + private readonly Guid _dbGuid; + + public OpportunityRoleAssignmentService( + CentralDbContext dbContext, + IIdServiceClient idServiceClient, + ISimpleSecurityService simpleSecurityService, + IClaimsPrincipalAccessor claimsPrincipalAccessor) + { + _dbContext = dbContext; + _idServiceClient = idServiceClient; + _simpleSecurityService = simpleSecurityService; + _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + _dbGuid = claimsPrincipalAccessor.GetDatabaseGuid(); + + } + public async Task AddPermissionsFromDefaultsAsync(long opportunityId, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(_dbGuid, cancellationToken); + + var defaultPermissions = await _simpleSecurityService.GetSimplePermissionDefaults(SecurableEntityType.Opportunity, cancellationToken); + _dbContext.OpportunityRoleAssignments.AddRange(defaultPermissions.Select(item => new OpportunityRoleAssignment(item, opportunityId, strataId))); + + await _dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task> GetRoleAssignmentsAsync(long opportunityId, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(_dbGuid, cancellationToken); + var roleAssignments = await _dbContext.OpportunityRoleAssignments.Where(p => p.OpportunityId == opportunityId && p.StrataId == strataId).OrderBy(item => item.IdentityName).Select(item => new OpportunityRoleAssignmentView(item)).ToListAsync(cancellationToken); + + return roleAssignments; + } + + public async Task GetUserRoleAssignmentsAsync(long opportunityId, CancellationToken cancellationToken) + { + if (_user.IsSdtEmployee()) + { + var sdtRole = new RoleAssignmentView(); + sdtRole.CanRead = true; + sdtRole.CanWrite = true; + sdtRole.CanSecure = true; + + return sdtRole; + } + + var strataId = await _idServiceClient.GetStrataId(_dbGuid, cancellationToken); + var userGroups = _user.Claims + .Where(c => c.Type == JwtClaimTypes.Role) + .Select(c => c.Value); + var role = await _dbContext.OpportunityRoleAssignments + .Where(r => r.OpportunityId == opportunityId && r.StrataId == strataId && (r.IdentityGuid.Equals(_user.GetUserGuid()) || userGroups.Contains(r.IdentityName))) + .GroupBy(r => new { r.OpportunityId }) + .Select(role => new RoleAssignmentView() + { + CanRead = role.Any(r => r.IsRead), + CanWrite = role.Any(r => r.IsWrite), + CanSecure = role.Any(r => r.IsSecureWrite) + }).FirstOrDefaultAsync(cancellationToken); + + return role; + } + + public async Task SaveRoleAssignmentsAsync(long opportunityId, List permissionSettings, CancellationToken cancellationToken) + { + var strataId = await _idServiceClient.GetStrataId(_dbGuid, cancellationToken); + var existingAssignments = await _dbContext.OpportunityRoleAssignments.Where(p => p.OpportunityId == opportunityId && p.StrataId == strataId).ToListAsync(cancellationToken); + + var newRoleAssigments = permissionSettings.Where(item => !existingAssignments.Exists(assignment => assignment.IdentityGuid == item.IdentityGuid)).ToList(); + var deleteRoleAssigments = existingAssignments.Where(item => !permissionSettings.Exists(assignment => assignment.IdentityGuid == item.IdentityGuid)); + var updateRoleAssigments = existingAssignments.Where(item => permissionSettings.Exists(assignment => assignment.IdentityGuid == item.IdentityGuid)); + + updateRoleAssigments.ForEach(item => + { + var updatedItem = permissionSettings.FirstOrDefault(role => role.IdentityGuid == item.IdentityGuid); + item.IsRead = updatedItem.CanRead; + item.IsWrite = updatedItem.CanWrite; + item.IsSecureWrite = updatedItem.CanSecure; + }); + + _dbContext.OpportunityRoleAssignments.AddRange(newRoleAssigments.Select(item => new OpportunityRoleAssignment(item, strataId))); + _dbContext.OpportunityRoleAssignments.RemoveRange(deleteRoleAssigments); + _dbContext.OpportunityRoleAssignments.UpdateRange(updateRoleAssigments); + + var result = await _dbContext.SaveChangesAsync(cancellationToken); + return result > 0; + } + + public async Task CopyTo(long fromOpportunityId, long toOpportunityId, CancellationToken cancellationToken) + { + var roleAssignments = await _dbContext.OpportunityRoleAssignments.Where(role => role.OpportunityId == fromOpportunityId).ToListAsync(cancellationToken); + + if (roleAssignments.Any()) + { + var newRoleAssignments = roleAssignments.Select(role => new OpportunityRoleAssignment(role, toOpportunityId)); + _dbContext.OpportunityRoleAssignments.AddRange(newRoleAssignments); + await _dbContext.SaveChangesAsync(cancellationToken); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/OpportunityRoleAssignmentView.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/OpportunityRoleAssignmentView.cs new file mode 100644 index 0000000..1a18381 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityRoleAssignment/OpportunityRoleAssignmentView.cs @@ -0,0 +1,30 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment +{ + public class OpportunityRoleAssignmentView : RoleAssignmentView + { + public long LootId { get; set; } + public Guid IdentityGuid { get; set; } + public string IdentityName { get; set; } + + public OpportunityRoleAssignmentView() { } + public OpportunityRoleAssignmentView(OpportunityRoleAssignment roleAssignment) + { + LootId = roleAssignment.OpportunityId; + IdentityGuid = roleAssignment.IdentityGuid; + IdentityName = roleAssignment.IdentityName; + CanRead = roleAssignment.IsRead; + CanWrite = roleAssignment.IsWrite; + CanSecure = roleAssignment.IsSecureWrite; + } + + } + + public class RoleAssignmentView + { + public bool CanRead { get; set; } + public bool CanWrite { get; set; } + public bool CanSecure { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityTypeWorkbooks/IOpportunityTypeWorkbookService.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityTypeWorkbooks/IOpportunityTypeWorkbookService.cs new file mode 100644 index 0000000..acd1140 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityTypeWorkbooks/IOpportunityTypeWorkbookService.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks +{ + public interface IOpportunityTypeWorkbookService + { + Task ContainsAsync(int opportunityType, int workbookId, CancellationToken cancellationToken); + Task> GetOpportunityTypeWorkbooksAsync(int opportunityType, CancellationToken cancellationToken); + Task> CreateOpportunityTypeWorkbooksAsync(IEnumerable opportunityTypeWorkbooks, CancellationToken cancellationToken); + Task DeleteAsync(int opportunityType, int workbookId, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityTypeWorkbooks/OpportunityTypeWorkbook.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityTypeWorkbooks/OpportunityTypeWorkbook.cs new file mode 100644 index 0000000..7dc5c1e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityTypeWorkbooks/OpportunityTypeWorkbook.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks +{ + public class OpportunityTypeWorkbook + { + public int StrataId { get; set; } + public int OpportunityType { get; set; } + public int WorkbookId { get; set; } + [NotMapped] + public string Name { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityTypeWorkbooks/OpportunityTypeWorkbookService.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityTypeWorkbooks/OpportunityTypeWorkbookService.cs new file mode 100644 index 0000000..6f4cf2c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityTypeWorkbooks/OpportunityTypeWorkbookService.cs @@ -0,0 +1,84 @@ +using ClosedXML.Excel; +using Microsoft.EntityFrameworkCore; +using MoreLinq; +using Strata.Analytics.Client; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks +{ + public class OpportunityTypeWorkbookService : IOpportunityTypeWorkbookService + { + private readonly CentralDbContext _centralDbContext; + private readonly IAnalyticsService _analyticsService; + private readonly ClaimsPrincipal _user; + + public OpportunityTypeWorkbookService(CentralDbContext centralDbContext, + IAnalyticsService analyticsService, + IClaimsPrincipalAccessor claimsPrincipalAccessor) + { + _centralDbContext = centralDbContext; + _analyticsService = analyticsService; + _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + } + + public async Task ContainsAsync(int opportunityType, int workbookId, CancellationToken cancellationToken) + { + return await _centralDbContext.OpportunityTypeWorkbooks.ContainsAsync(new OpportunityTypeWorkbook() + { + StrataId = _user.GetStrataId(), + OpportunityType = opportunityType, + WorkbookId = workbookId + }, cancellationToken); + } + + public async Task> GetOpportunityTypeWorkbooksAsync(int opportunityType, CancellationToken cancellationToken) + { + var strataId = _user.GetStrataId(); + var opportunityTypeWorkbooks = await _centralDbContext.OpportunityTypeWorkbooks + .Where(ow => ow.StrataId == strataId && ow.OpportunityType == opportunityType) + .ToListAsync(cancellationToken); + + // get workbook names + var workbooks = await _analyticsService.GetWorkbooksAsync(false, cancellationToken); + var existingOpportunityTypeWorkbooks = new List(); + foreach (var opportunityTypeWorkbook in opportunityTypeWorkbooks) + { + // ignore deleted workbooks for now + var workbookName = workbooks.FirstOrDefault(x => x.StrataId == strataId && x.WorkbookId == opportunityTypeWorkbook.WorkbookId)?.Name; + if (workbookName != null) + { + opportunityTypeWorkbook.Name = workbookName; + existingOpportunityTypeWorkbooks.Add(opportunityTypeWorkbook); + } + } + + return existingOpportunityTypeWorkbooks; + } + + public async Task> CreateOpportunityTypeWorkbooksAsync(IEnumerable opportunityTypeWorkbooks, CancellationToken cancellationToken) + { + var strataId = _user.GetStrataId(); + opportunityTypeWorkbooks.ForEach(x => x.StrataId = strataId); + await _centralDbContext.OpportunityTypeWorkbooks.AddRangeAsync(opportunityTypeWorkbooks, cancellationToken); + await _centralDbContext.SaveChangesAsync(); + return opportunityTypeWorkbooks; + } + + public async Task DeleteAsync(int opportunityType, int workbookId, CancellationToken cancellationToken) + { + var opportunityTypeWorkbook = await _centralDbContext.OpportunityTypeWorkbooks + .Where(x => x.StrataId == _user.GetStrataId() && + x.OpportunityType == opportunityType && + x.WorkbookId == workbookId).SingleOrDefaultAsync(); + _centralDbContext.Remove(opportunityTypeWorkbook); + await _centralDbContext.SaveChangesAsync(cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/IOpportunityWorkbook.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/IOpportunityWorkbook.cs new file mode 100644 index 0000000..40248ea --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/IOpportunityWorkbook.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.OpportunityWorkbooks +{ + internal interface IOpportunityWorkbook + { + public int WorkbookId { get; set; } + public string Name { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/IOpportunityWorkbookService.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/IOpportunityWorkbookService.cs new file mode 100644 index 0000000..7a2479e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/IOpportunityWorkbookService.cs @@ -0,0 +1,23 @@ +using Strata.Analytics.Models.Workbooks; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OpportunityWorkbooks +{ + public interface IOpportunityWorkbookService + { + Task ContainsAsync(Guid opportunityGuid, int workbookId, CancellationToken cancellationToken); + Task> GetAllOpportunityWorkbooksAsync(bool isHidden, CancellationToken cancellationToken); + Task> GetOpportunityWorkbooksAsync(Guid opportunityGuid, CancellationToken cancellationToken); + Task> CreateOpportunityWorkbooksAsync(IEnumerable opportunityWorkbooks, CancellationToken cancellationToken); + Task DeleteAsync(Guid opportunityGuid, int workbookId, CancellationToken cancellationToken); + Task ContainsStrategicAsync(long opportunityId, int workbookId, CancellationToken cancellationToken); + Task> GetStrategicOpportunityWorkbooksAsync(long opportunityId, CancellationToken cancellationToken); + Task> CreateStrategicOpportunityWorkbooksAsync(IEnumerable opportunityWorkbooks, CancellationToken cancellationToken); + Task DeleteStrategicOpportunityAsync(long opportunityId, int workbookId, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/OpportunityWorkbook.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/OpportunityWorkbook.cs new file mode 100644 index 0000000..cd08b07 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/OpportunityWorkbook.cs @@ -0,0 +1,15 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.OpportunityWorkbooks +{ + public class OpportunityWorkbook : IOpportunityWorkbook, IWithStrataId + { + public int StrataId { get; set; } + public Guid OpportunityGuid { get; set; } + public int WorkbookId { get; set; } + [NotMapped] + public string Name { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/OpportunityWorkbookService.cs b/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/OpportunityWorkbookService.cs new file mode 100644 index 0000000..0d54244 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OpportunityWorkbooks/OpportunityWorkbookService.cs @@ -0,0 +1,148 @@ +using ClosedXML.Excel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.Analytics.Client; +using Strata.Analytics.Models.Workbooks; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OpportunityWorkbooks +{ + public class OpportunityWorkbookService( + CentralDbContext centralDbContext, + IAnalyticsService analyticsService, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + ILogger logger) : IOpportunityWorkbookService + { + private readonly ClaimsPrincipal _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + + public async Task ContainsAsync(Guid opportunityGuid, int workbookId, CancellationToken cancellationToken) + { + return await centralDbContext.OpportunityWorkbooks + .ContainsAsync(new OpportunityWorkbook() + { + StrataId = _user.GetStrataId(), + WorkbookId = workbookId, + OpportunityGuid = opportunityGuid + }, cancellationToken); + } + + public async Task> GetAllOpportunityWorkbooksAsync(bool isHidden, CancellationToken cancellationToken) + { + return await analyticsService.GetWorkbooksAsync(isHidden, cancellationToken); + } + + public async Task> GetOpportunityWorkbooksAsync(Guid opportunityGuid, CancellationToken cancellationToken) + { + var strataId = _user.GetStrataId(); + var opportunityWorkbooks = await centralDbContext.OpportunityWorkbooks + .Where(ow => ow.StrataId == strataId && ow.OpportunityGuid == opportunityGuid) + .ToListAsync(cancellationToken); + var existingOpportunityWorkbooks = await GetWorkbooksWithName(strataId, opportunityWorkbooks, cancellationToken); + + return existingOpportunityWorkbooks; + } + + public async Task> CreateOpportunityWorkbooksAsync(IEnumerable opportunityWorkbooks, CancellationToken cancellationToken) + { + return await AddWorkbooks(opportunityWorkbooks, cancellationToken); + } + + public async Task DeleteAsync(Guid opportunityGuid, int workbookId, CancellationToken cancellationToken) + { + var opportunityWorkbook = await centralDbContext.OpportunityWorkbooks + .Where(x => x.StrataId == _user.GetStrataId() && + x.OpportunityGuid == opportunityGuid && + x.WorkbookId == workbookId).SingleOrDefaultAsync(); + await RemoveWorkbook(opportunityWorkbook, cancellationToken); + } + + #region Strategic Workbooks + + public async Task ContainsStrategicAsync(long opportunityId, int workbookId, CancellationToken cancellationToken) + { + return await centralDbContext.StrategicOpportunityWorkbooks + .AnyAsync(wb => wb.WorkbookId == workbookId && wb.OpportunityId == opportunityId, cancellationToken); + } + + public async Task> GetStrategicOpportunityWorkbooksAsync(long opportunityId, CancellationToken cancellationToken) + { + try + { + var strataId = _user.GetStrataId(); + var opportunity = centralDbContext.StrategicOpportunities.Include(s => s.Workbooks) + .FirstOrDefault(so => so.StrataId == strataId && so.OpportunityId == opportunityId); + + var existingOpportunityWorkbooks = await GetWorkbooksWithName(strataId, opportunity.Workbooks.ToList(), cancellationToken); + + return existingOpportunityWorkbooks.Select(wb => wb.MapToStrategicOpportunityWorkbookDto()); + } + catch (Exception ex) + { + logger.LogServiceError(ex); + throw; + } + } + + public async Task> CreateStrategicOpportunityWorkbooksAsync(IEnumerable opportunityWorkbooks, CancellationToken cancellationToken) + { + return (await AddWorkbooks(opportunityWorkbooks, cancellationToken)).Select(wb => wb.MapToStrategicOpportunityWorkbookDto()); + } + + public async Task DeleteStrategicOpportunityAsync(long opportunityId, int workbookId, CancellationToken cancellationToken) + { + var opportunityWorkbook = await centralDbContext.StrategicOpportunityWorkbooks + .Where(x => x.OpportunityId == opportunityId && + x.WorkbookId == workbookId).SingleOrDefaultAsync(); + await RemoveWorkbook(opportunityWorkbook, cancellationToken); + } + + #endregion + + private async Task> GetWorkbooksWithName(int strataId, List opportunityWorkbooks, CancellationToken cancellationToken) where T : IOpportunityWorkbook + { + // get workbook names + var workbooks = await analyticsService.GetWorkbooksAsync(false, cancellationToken); + var existingOpportunityWorkbooks = new List(); + foreach (var opportunityWorkbook in opportunityWorkbooks) + { + // ignore deleted workbooks for now + var workbookName = workbooks.FirstOrDefault(x => x.StrataId == strataId && x.WorkbookId == opportunityWorkbook.WorkbookId)?.Name; + if (workbookName != null) + { + opportunityWorkbook.Name = workbookName; + existingOpportunityWorkbooks.Add(opportunityWorkbook); + } + } + + return existingOpportunityWorkbooks; + } + + private async Task> AddWorkbooks(IEnumerable opportunityWorkbooks, CancellationToken cancellationToken) where T : class, IWithStrataId + { + var strataId = _user.GetStrataId(); + opportunityWorkbooks.ForEach(x => x.StrataId = strataId); + await centralDbContext.Set().AddRangeAsync(opportunityWorkbooks, cancellationToken); + await centralDbContext.SaveChangesAsync(); + return opportunityWorkbooks; + } + + private async Task RemoveWorkbook(T opportunityWorkbook, CancellationToken cancellationToken) where T : class + { + centralDbContext.Set().Remove(opportunityWorkbook); + await centralDbContext.SaveChangesAsync(cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/IOpportunityMetricBase.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/IOpportunityMetricBase.cs new file mode 100644 index 0000000..47cfdc0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/IOpportunityMetricBase.cs @@ -0,0 +1,19 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined +{ + public interface IOpportunityMetricBase + { + int EncounterGroup { get; } + IEnumerable CaseTypeIds { get; } + IEnumerable PatientPopulationIds { get; } + IEnumerable ChargeCodeIds { get; } + string OpportunityLevel { get; set; } + bool IsBaseline { get; set; } + IEnumerable OpportunityLevelIds { get; } + string OpportunityLevelIdAsCSV { get; } + OpportunityLevel OpportunityLevelEnum { get; } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/IOpportunityMetricCohortService.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/IOpportunityMetricCohortService.cs new file mode 100644 index 0000000..1bc7af9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/IOpportunityMetricCohortService.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.OrgDefined.Queries; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined +{ + public interface IOpportunityMetricCohortService + { + Task> GetChargeCodeOpportunityMetricCohortData(Configuration.Configuration configuration, IOpportunityMetricBase data, string innerJoin, CancellationToken cancellationToken); + Task> GetEncounterOpportunityMetricCohortData(Configuration.Configuration configuration, IOpportunityMetricBase data, string innerJoin, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/OpportunityMetricCohortService.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/OpportunityMetricCohortService.cs new file mode 100644 index 0000000..97c7049 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/OpportunityMetricCohortService.cs @@ -0,0 +1,65 @@ +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.OrgDefined.Queries; +using Strata.ContinuousImprovement.Biz.Settings; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined +{ + public class OpportunityMetricCohortService : IOpportunityMetricCohortService + { + protected readonly ISettingsService _settingsService; + protected readonly IAsyncDbContextFactory _jazzDbContextFactory; + protected readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext; + protected readonly ILogger _logger; + + public OpportunityMetricCohortService( + ISettingsService settingsService, + IAsyncDbContextFactory jazzDbContextFactory, + ISnowflakeDatabaseContext snowflakeDatabaseContext, + ILogger logger) + { + _settingsService = settingsService; + _jazzDbContextFactory = jazzDbContextFactory; + _snowflakeDatabaseContext = snowflakeDatabaseContext; + _logger = logger; + } + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _jazzDbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task> GetEncounterOpportunityMetricCohortData(Configuration.Configuration configuration, IOpportunityMetricBase data, string innerJoin, CancellationToken cancellationToken) + => await GetOpportunityMetricCohortData(configuration, data, innerJoin, Generics.OpportunityTypes.Encounter, cancellationToken); + + public async Task> GetChargeCodeOpportunityMetricCohortData(Configuration.Configuration configuration, IOpportunityMetricBase data, string innerJoin, CancellationToken cancellationToken) + => await GetOpportunityMetricCohortData(configuration, data, innerJoin, Generics.OpportunityTypes.ChargeCode, cancellationToken); + + internal async Task> GetOpportunityMetricCohortData(Configuration.Configuration configuration, IOpportunityMetricBase data, string innerJoin, Generics.OpportunityTypes opportunityType, CancellationToken cancellationToken) + { + await using var jazzDbContext = GetDbContext(cancellationToken).Result; + var queryBuilder = OpportunityMetricCohortQueryBase.GetOpportunityMetricQuery(configuration, data, opportunityType, _settingsService, jazzDbContext); + var query = await queryBuilder.GetSnowflakeSqlTemplate(innerJoin, cancellationToken); + var startDate = data.IsBaseline + ? configuration.GetBaselineStartDate(opportunityType) + : configuration.GetTrackingStartDate(opportunityType); + var endDate = data.IsBaseline + ? configuration.GetBaselineEndDate(opportunityType) + : configuration.GetTrackingEndDate(); + + var queryParams = new + { + IsBaseline = data.IsBaseline, + startDate = $"{startDate:yyyy-MM-dd}", + endDate = $"{endDate:yyyy-MM-dd}" + }; + var results = await _snowflakeDatabaseContext.QueryAsync(query, "Opportunity-Metric-Cohort", queryParams); + return results; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/IOpportunityMetricCohortQueryBase.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/IOpportunityMetricCohortQueryBase.cs new file mode 100644 index 0000000..124bca8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/IOpportunityMetricCohortQueryBase.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined.Queries +{ + public interface IOpportunityMetricCohortQueryBase + { + Task GetSnowflakeSqlTemplate(string innerJoin, CancellationToken cancellationToken); + Task GetSqlTemplate(string innerJoin, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricBase.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricBase.cs new file mode 100644 index 0000000..5016c1f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricBase.cs @@ -0,0 +1,33 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined.Queries +{ + public class OpportunityMetricBase : IOpportunityMetricBase + { + public int EncounterGroup { get; set; } + + public IEnumerable CaseTypeIds { get; set; } + + public IEnumerable PatientPopulationIds { get; set; } + + public IEnumerable ChargeCodeIds { get; set; } + + public string OpportunityLevel { get; set; } + public bool IsBaseline { get; set; } + + public IEnumerable OpportunityLevelIds { get; set; } + + public string OpportunityLevelIdAsCSV { get; set; } + + [JsonIgnore] + [Obsolete("Value is incorrect when coming from Jazz, will be removed in future")] + // TODO: remove OpportunityLevelEnum property from this class and regression test + public OpportunityLevel OpportunityLevelEnum => + Enum.Parse(OpportunityLevel, true); + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortEntityQuery.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortEntityQuery.cs new file mode 100644 index 0000000..e69ca3f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortEntityQuery.cs @@ -0,0 +1,77 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Settings; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined.Queries +{ + public class OpportunityMetricCohortEntityQuery : OpportunityMetricCohortQueryBase, IOpportunityMetricCohortQueryBase + { + private string RollupJoin { get; set; } + private string RollupColumn { get; set; } + public virtual bool HasRollupId => Config.IsUsingCustomEntityDimension; + public virtual string DepartmentRollupGlobalId => Config.DepartmentRollupGlobalId; + + + public OpportunityMetricCohortEntityQuery(Configuration.Configuration config, IOpportunityMetricBase data, OpportunityTypes opportunityType, ISettingsService settingsService) + : base(config, data, opportunityType, settingsService) + { + RollupJoin = GetRollupJoin(); + RollupColumn = GetRollupColumn(); + } + + public override async Task GetSqlTemplate(string innerJoin, CancellationToken cancellationToken = default) + { + var revenueColumn = await base.RevenueColumn(cancellationToken); + var costColumn = await base.CostColumn(cancellationToken); + var billingJoin = await base.GetBillingJoin(cancellationToken); + var billing = await base.GetSqlTemplate(innerJoin, cancellationToken); + + return $@"{billing} +RevenueTotals AS ( +SELECT {RollupColumn} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], SUM({revenueColumn}) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +{RollupJoin} +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} +AND {RollupColumn} IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY {RollupColumn}, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT {RollupColumn} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +SUM({costColumn}) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{billingJoin} +{RollupJoin} +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} + AND {RollupColumn} IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY {RollupColumn}, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + private string GetRollupJoin() + { + return HasRollupId +#pragma warning disable S3358 // Ternary operators should not be nested + ? DepartmentRollupGlobalId == "Location" + ? string.Empty + : "INNER JOIN [fw].[DimDepartment] de ON de.DepartmentID = fpes.HighestVariableCostDepartmentID" +#pragma warning restore S3358 // Ternary operators should not be nested + : "INNER JOIN clientdss.DimEntity de ON de.EntityID = fpes.EntityID"; + } + + private string GetRollupColumn() + { + return HasRollupId +#pragma warning disable S3358 // Ternary operators should not be nested + ? DepartmentRollupGlobalId == "Location" + ? "fpes.LocationID" + : $"de.{DepartmentRollupGlobalId.Replace(" ", string.Empty)}ID" +#pragma warning restore S3358 // Ternary operators should not be nested + : "de.FwEntityID"; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortOptions.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortOptions.cs new file mode 100644 index 0000000..9a90f59 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortOptions.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined.Queries +{ + [ExcludeFromCodeCoverage] + public class OpportunityMetricCohortOptions + { + public Guid ConfigurationGuid { get; set; } + public OpportunityMetricBase Data { get; set; } + public string InnerJoin { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortOrganizationQuery.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortOrganizationQuery.cs new file mode 100644 index 0000000..cd2214f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortOrganizationQuery.cs @@ -0,0 +1,43 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Settings; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined.Queries +{ + public class OpportunityMetricCohortOrganizationQuery : OpportunityMetricCohortQueryBase, IOpportunityMetricCohortQueryBase + { + public OpportunityMetricCohortOrganizationQuery(Configuration.Configuration config, IOpportunityMetricBase data, OpportunityTypes opportunityType, ISettingsService settingsService) + : base(config, data, opportunityType, settingsService) + { + } + + public override async Task GetSqlTemplate(string innerJoin, CancellationToken cancellationToken = default) + { + var revenueColumn = await base.RevenueColumn(cancellationToken); + var costColumn = await base.CostColumn(cancellationToken); + var billingJoin = await base.GetBillingJoin(cancellationToken); + var billing = await base.GetSqlTemplate(innerJoin, cancellationToken); + + return $@"{billing} +RevenueTotals AS ( +SELECT 0 LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], SUM({revenueColumn}) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} +GROUP BY DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT 0 LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +SUM({costColumn}) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{billingJoin} +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} +GROUP BY DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortProviderQuery.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortProviderQuery.cs new file mode 100644 index 0000000..6a6353a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortProviderQuery.cs @@ -0,0 +1,47 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Settings; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined.Queries +{ + public class OpportunityMetricCohortProviderQuery : OpportunityMetricCohortQueryBase, IOpportunityMetricCohortQueryBase + { + public OpportunityMetricCohortProviderQuery(Configuration.Configuration config, IOpportunityMetricBase data, OpportunityTypes opportunityType, ISettingsService settingsService) + : base(config, data, opportunityType, settingsService) + { + } + + public override async Task GetSqlTemplate(string innerJoin, CancellationToken cancellationToken = default) + { + var revenueColumn = await base.RevenueColumn(cancellationToken); + var costColumn = await base.CostColumn(cancellationToken); + var billingJoin = await base.GetBillingJoin(cancellationToken); + var billing = await base.GetSqlTemplate(innerJoin, cancellationToken); + + return $@"{billing} +RevenueTotals AS ( +SELECT dp.PhysicianID LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], SUM({revenueColumn}) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +INNER JOIN dss.DimPhysician dp on dp.PhysicianID = fpes.PrimaryPhysicianID +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} +AND dp.PhysicianID IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY dp.PhysicianID, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT dp.PhysicianID LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +SUM({costColumn}) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{billingJoin} +INNER JOIN dss.DimPhysician dp on dp.PhysicianID = fpes.PrimaryPhysicianID +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} + AND dp.PhysicianID IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY dp.PhysicianID, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortQueryBase.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortQueryBase.cs new file mode 100644 index 0000000..51089d8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortQueryBase.cs @@ -0,0 +1,114 @@ +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Encounter.Initiatives; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Settings; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined.Queries +{ + public class OpportunityMetricCohortQueryBase : IOpportunityMetricCohortQueryBase + { + protected readonly Configuration.Configuration Config; + protected readonly IOpportunityMetricBase Data; + protected readonly ISettingsService SettingsService; + + public OpportunityMetricCohortQueryBase(Configuration.Configuration config, IOpportunityMetricBase data, OpportunityTypes opportunityType, ISettingsService settingsService) + { + Config = config; + Data = data; + SettingsService = settingsService; + } + + public static OpportunityMetricCohortQueryBase GetOpportunityMetricQuery(Configuration.Configuration config, IOpportunityMetricBase data, OpportunityTypes opportunityType, ISettingsService settingsService, JazzDbContext jazzDbContext = default) + { + switch (data.OpportunityLevel) + { + case nameof(OpportunityLevel.Organization): + return new OpportunityMetricCohortOrganizationQuery(config, data, opportunityType, settingsService); + case nameof(OpportunityLevel.Entity): + return new OpportunityMetricCohortEntityQuery(config, data, opportunityType, settingsService); + case nameof(OpportunityLevel.Specialty): + return new OpportunityMetricCohortSpecialtyQuery(config, data, opportunityType, settingsService); + case nameof(OpportunityLevel.Provider): + return new OpportunityMetricCohortProviderQuery(config, data, opportunityType, settingsService); + case nameof(OpportunityLevel.ServiceLine): + return new OpportunityMetricCohortServiceLineQuery(config, data, opportunityType, settingsService, jazzDbContext); + default: + return new OpportunityMetricCohortQueryBase(config, data, opportunityType, settingsService); + } + } + + public virtual async Task GetSnowflakeSqlTemplate(string innerJoin, CancellationToken cancellationToken) + { + var sqlSyntax = await GetSqlTemplate(innerJoin, cancellationToken); + return sqlSyntax.SnowflakeSyntax(); + } + + public virtual async Task GetSqlTemplate(string innerJoin, CancellationToken cancellationToken = default) + { + var settings = await SettingsService.SystemSettingsAsync(cancellationToken); + var setting = settings.TryGetValue("EncounterInitiativesCostType", out var ret) ? ret : "0"; + var costType = (EncounterInitiativeCostType)Convert.ToInt32(setting); + if (!(costType is EncounterInitiativeCostType.DetailBilling)) + { + return await Task.FromResult("WITH "); + } + return await Task.FromResult(@"WITH billing as ( + SELECT + EncounterId, + FixedDirectCost + ,FixedIndirectCost + ,VariableDirectCost + ,VariableIndirectCost + ,FixedDirectCost + FixedIndirectCost FixedCost + ,VariableDirectCost + VariableIndirectCost VariableCost + ,FixedDirectCost + VariableDirectCost DirectCost + ,FixedIndirectCost + VariableIndirectCost IndirectCost + ,TotalCost + FROM cci.EncounterChargeCodeCosts +), +"); + } + + protected virtual async Task RevenueColumn(CancellationToken cancellationToken) + { + var settings = await SettingsService.SystemSettingsAsync(cancellationToken); + var setting = settings.TryGetValue("EncounterInitiativesRevenueCSF", out var ret) ? ret : "0"; + return Convert.ToBoolean(Convert.ToInt32(setting)) ? "fpes.CustomEncounterInitiativesRevenue" : "fpes.NetRevenue"; + } + + protected virtual async Task CostColumn(CancellationToken cancellationToken) + { + var settings = await SettingsService.SystemSettingsAsync(cancellationToken); + var setting = settings.TryGetValue("EncounterInitiativesCostType", out var ret) ? ret : "0"; + var costType = (EncounterInitiativeCostType)Convert.ToInt32(setting); + switch (costType) + { + case EncounterInitiativeCostType.EncounterSummary: + return Config.IsUsingIndirectCost ? "fpes.VariableIndirectCost + fpes.VariableDirectCost" : "fpes.VariableDirectCost"; + case EncounterInitiativeCostType.CSF: + return "fpes.CustomEncounterInitiativesCost"; + case EncounterInitiativeCostType.DetailBilling: + return Config.IsUsingIndirectCost ? "billing.VariableIndirectCost + billing.VariableDirectCost" : "billing.VariableDirectCost"; + } + + return ""; + } + + protected virtual async Task GetBillingJoin(CancellationToken cancellationToken) + { + var settings = await SettingsService.SystemSettingsAsync(cancellationToken); + var setting = settings.TryGetValue("EncounterInitiativesCostType", out var ret) ? ret : "0"; + var costType = (EncounterInitiativeCostType)Convert.ToInt32(setting); + if (costType is EncounterInitiativeCostType.DetailBilling) + { + return "INNER JOIN billing ON billing.EncounterId = fpes.EncounterId "; + } + return ""; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortServiceLineQuery.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortServiceLineQuery.cs new file mode 100644 index 0000000..e364697 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortServiceLineQuery.cs @@ -0,0 +1,97 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Score; +using Strata.ContinuousImprovement.Biz.Settings; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined.Queries +{ + public class OpportunityMetricCohortServiceLineQuery : OpportunityMetricCohortQueryBase, IOpportunityMetricCohortQueryBase + { + private readonly JazzDbContext _jazzDbContext; + + public OpportunityMetricCohortServiceLineQuery(Configuration.Configuration config, IOpportunityMetricBase data, OpportunityTypes opportunityType, ISettingsService settingsService, JazzDbContext jazzDbContext) + : base(config, data, opportunityType, settingsService) + { + _jazzDbContext = jazzDbContext; + } + + public override async Task GetSqlTemplate(string innerJoin, CancellationToken cancellationToken = default) + { + var serviceLineDimension = await Config.GetServiceLineDimension(_jazzDbContext, CancellationToken.None); + if (serviceLineDimension.FriendlyName == Dimension.Sg2ServiceLineCrosswalk) + return await GetSg2SqlTemplate(innerJoin, serviceLineDimension, cancellationToken); + return await GetServiceLineSqlTemplate(innerJoin, serviceLineDimension, cancellationToken); + } + + public async Task GetServiceLineSqlTemplate(string innerJoin, ScoreDimension serviceLineDimension, CancellationToken cancellationToken) + { + var revenueColumn = await base.RevenueColumn(cancellationToken); + var costColumn = await base.CostColumn(cancellationToken); + var billingJoin = await base.GetBillingJoin(cancellationToken); + var billing = await base.GetSqlTemplate(innerJoin, cancellationToken); + + var serviceLine = ""; + serviceLine = string.IsNullOrEmpty(serviceLineDimension.DefaultKey?.Name) ? "ServiceLineID" : serviceLine; + + return $@"{billing} +RevenueTotals AS ( +SELECT fpes.{serviceLine} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], SUM({revenueColumn}) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} +AND fpes.{serviceLine} IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY fpes.{serviceLine}, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT fpes.{serviceLine} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], +COUNT(DISTINCT(fpes.EncounterID)) Volume, SUM({costColumn}) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{billingJoin} +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} + AND fpes.{serviceLine} IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY fpes.{serviceLine}, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + public async Task GetSg2SqlTemplate(string innerJoin, ScoreDimension serviceLineDimension, CancellationToken cancellationToken) + { + var revenueColumn = await base.RevenueColumn(cancellationToken); + var costColumn = await base.CostColumn(cancellationToken); + var billingJoin = await base.GetBillingJoin(cancellationToken); + var billing = await base.GetSqlTemplate(innerJoin, cancellationToken); + var defaultKey = await _jazzDbContext.ScoreAttributes.FirstOrDefaultAsync(sa => sa.AttributeGuid == serviceLineDimension.DefaultKeyGuid, cancellationToken); + var serviceLine = defaultKey.Name; + + return $@"{billing} + RevenueTotals AS ( +SELECT sl.{serviceLine} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], SUM({revenueColumn}) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +INNER JOIN dss.DimSg2CareFamily cf ON cf.Sg2CAREFamilyID = fpes.Sg2CAREFamilyID +INNER JOIN dss.DimSg2ServiceLineCrosswalk sl ON sl.Sg2ServiceLine = cf.ServiceLine +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} +AND sl.{serviceLine} IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY sl.{serviceLine}, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT sl.{serviceLine} LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +SUM({costColumn}) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{billingJoin} +INNER JOIN dss.DimSg2CareFamily cf ON cf.Sg2CAREFamilyID = fpes.Sg2CAREFamilyID +INNER JOIN dss.DimSg2ServiceLineCrosswalk sl ON sl.Sg2ServiceLine = cf.ServiceLine +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} + AND sl.{serviceLine} IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY sl.{serviceLine}, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortSpecialtyQuery.cs b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortSpecialtyQuery.cs new file mode 100644 index 0000000..9f337ce --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/OrgDefined/Queries/OpportunityMetricCohortSpecialtyQuery.cs @@ -0,0 +1,49 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Settings; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.OrgDefined.Queries +{ + public class OpportunityMetricCohortSpecialtyQuery : OpportunityMetricCohortQueryBase, IOpportunityMetricCohortQueryBase + { + public OpportunityMetricCohortSpecialtyQuery(Configuration.Configuration config, IOpportunityMetricBase data, OpportunityTypes opportunityType, ISettingsService settingsService) + : base(config, data, opportunityType, settingsService) + { + } + + public override async Task GetSqlTemplate(string innerJoin, CancellationToken cancellationToken = default) + { + var revenueColumn = await base.RevenueColumn(cancellationToken); + var costColumn = await base.CostColumn(cancellationToken); + var billingJoin = await base.GetBillingJoin(cancellationToken); + var billing = await base.GetSqlTemplate(innerJoin, cancellationToken); + var primaryPhysicianJoin = "INNER JOIN dss.DimPhysician pp ON pp.PhysicianID = fpes.PrimaryPhysicianID"; + + return $@"{billing} +RevenueTotals AS ( +SELECT ps.PhysicianSpecialtyID LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], SUM({revenueColumn}) Revenue +FROM clientdss.FactPatientEncounterSummary fpes +{primaryPhysicianJoin} +INNER JOIN dss.DimPhysicianSpecialty ps ON ps.PhysicianSpecialtyID = pp.PhysicianSpecialtyID +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} +AND ps.PhysicianSpecialtyID IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY ps.PhysicianSpecialtyID, DATEPART(MONTH, fpes.DischargeDateTime) +) + +SELECT rt.Revenue, q.* FROM ( +SELECT ps.PhysicianSpecialtyID LevelID, DATEPART(MONTH, fpes.DischargeDateTime) [Month], COUNT(DISTINCT(fpes.EncounterID)) Volume, +SUM({costColumn}) Cost, @IsBaseline IsBaseline +FROM clientdss.FactPatientEncounterSummary fpes +{billingJoin} +{primaryPhysicianJoin} +INNER JOIN dss.DimPhysicianSpecialty ps ON ps.PhysicianSpecialtyID = pp.PhysicianSpecialtyID +WHERE fpes.DischargeDateTime >= @startDate AND fpes.DischargeDateTime < @endDate +{innerJoin} + AND ps.PhysicianSpecialtyID IN ({Data.OpportunityLevelIdAsCSV}) +GROUP BY ps.PhysicianSpecialtyID, DATEPART(MONTH, fpes.DischargeDateTime)) q +INNER JOIN RevenueTotals rt ON rt.LevelID = q.LevelID AND rt.Month = q.Month"; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Pagination/IQueryableExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Pagination/IQueryableExtensions.cs new file mode 100644 index 0000000..7afb0b9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Pagination/IQueryableExtensions.cs @@ -0,0 +1,87 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Pagination +{ + public static class IQueryableExtensions + { + public static IQueryable Where(this IQueryable query, IEnumerable>> filters) + { + foreach (var e in filters) + { + query = query.Where(e); + } + return query; + } + + public static IQueryable OrderBy(this IQueryable 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 OrderBy(this IQueryable query, string propertyName) => + query.OrderBy(ToLambda(propertyName)); + + public static IOrderedQueryable OrderByDescending(this IQueryable query, string propertyName) => + query.OrderByDescending(ToLambda(propertyName)); + + /// + /// Asynchronously deletes any element of a sequence satisfies a condition. + /// + /// + /// + /// Multiple active operations on the same context instance are not supported. Use to ensure + /// that any asynchronous operations have completed before calling another method on this context. + /// See Avoiding DbContext threading issues for more information and examples. + /// + /// + /// See Querying data with EF Core for more information and examples. + /// + /// + /// The type of the elements of . + /// An whose elements to test for a condition. + /// A function to test each element for a condition. + /// A to observe while waiting for the task to complete. + /// + /// A task that represents the asynchronous operation. + /// The task if any elements in the source sequence pass the test in the specified + /// predicate; otherwise, . + /// + /// + /// or is . + /// + /// If the is canceled. + public static async Task ExecuteDeleteAsync( + this IQueryable source, + Expression> predicate, + CancellationToken cancellationToken = default) + { + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + await source.Where(predicate).ExecuteDeleteAsync(cancellationToken); + } + + private static Expression> ToLambda(string propertyName) + { + var parameter = Expression.Parameter(typeof(T)); + var property = Expression.Property(parameter, propertyName); + var propAsObject = Expression.Convert(property, typeof(object)); + return Expression.Lambda>(propAsObject, parameter); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Pagination/PagingExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Pagination/PagingExtensions.cs new file mode 100644 index 0000000..d1fa9c7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Pagination/PagingExtensions.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ApiLib.Standard.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Pagination +{ + public static class PagingExtensions + { + + public static async Task> ToPagedAsync(this IQueryable 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() { Data = items, PagingInfo = pagingInfo }; + } + + public static PagedApiResponse ToFilteredPagedList(this IList list, PagingOptions pagingOptions, List>> filters) + { + var sortedFilteredList = pagingOptions.SortOrder == SortOrder.Asc + ? list.AsQueryable().Where(filters).OrderBy(pagingOptions.SortField) + : list.AsQueryable().Where(filters).OrderByDescending(pagingOptions.SortField); + var pagedList = sortedFilteredList.Skip(pagingOptions.First).Take(pagingOptions.Rows).ToList(); + + var pagingInfo = new PagingInfo() + { + PageNumber = (pagingOptions.Rows == 0 ? 0 : pagingOptions.First / pagingOptions.Rows) + 1, + ResultsPerPage = pagingOptions.Rows, + TotalNumberOfResults = sortedFilteredList.Count() + }; + return new PagedApiResponse() { Data = pagedList, PagingInfo = pagingInfo }; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Pagination/PagingOptions.cs b/src/Strata.ContinuousImprovement.Biz/Pagination/PagingOptions.cs new file mode 100644 index 0000000..f5728ab --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Pagination/PagingOptions.cs @@ -0,0 +1,20 @@ +namespace Strata.ContinuousImprovement.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; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Pagination/SortOptions.cs b/src/Strata.ContinuousImprovement.Biz/Pagination/SortOptions.cs new file mode 100644 index 0000000..cf24a32 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Pagination/SortOptions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.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; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Pagination/SortOrder.cs b/src/Strata.ContinuousImprovement.Biz/Pagination/SortOrder.cs new file mode 100644 index 0000000..f941050 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Pagination/SortOrder.cs @@ -0,0 +1,10 @@ + + +namespace Strata.ContinuousImprovement.Biz.Pagination +{ + public enum SortOrder + { + Asc = 1, + Desc = -1 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PatientTypeMember/PatientType.cs b/src/Strata.ContinuousImprovement.Biz/PatientTypeMember/PatientType.cs new file mode 100644 index 0000000..06eb523 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PatientTypeMember/PatientType.cs @@ -0,0 +1,11 @@ + + +namespace Strata.ContinuousImprovement.Biz.PatientType +{ + public class PatientTypeMember + { + public int PatientTypeID { get; set; } + public int PatientTypeRollupID { get; set; } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Initiatives/IPRInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Initiatives/IPRInitiativeService.cs new file mode 100644 index 0000000..fb643a6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Initiatives/IPRInitiativeService.cs @@ -0,0 +1,9 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Initiatives +{ + public interface IPRInitiativeService : IInitiativeCreateService + { + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Initiatives/PRInitiativeService.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Initiatives/PRInitiativeService.cs new file mode 100644 index 0000000..dab5dd7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Initiatives/PRInitiativeService.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Initiatives +{ + public class PRInitiativeService : InitiativeCreateService, IPRInitiativeService + { + public PRInitiativeService( + IAsyncDbContextFactory dbContextFactory, + IInitiativeSavingsService initiativeSavingsService, + IAuthorizationServiceClient authorizationServiceClient, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + ILogger logger) + : base(dbContextFactory, initiativeSavingsService, authorizationServiceClient, updateFactOpportunitySavingsService, logger) + { + } + + public override async Task GetOpportunity(Guid opportunityGuid, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + return await dbContext.PayrollOpportunities.SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken) as IOpportunity; + } + + public override async Task SaveOpportunity(Guid opportunityGuid, Guid initiativeGuid, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + var opportunity = await dbContext.PayrollOpportunities.SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunity == null) { return; } + + opportunity.InitiativeGuid = initiativeGuid; + await dbContext.SaveChangesAsync(cancellationToken); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Initiatives/PayrollInitiativeDto.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Initiatives/PayrollInitiativeDto.cs new file mode 100644 index 0000000..f2239b6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Initiatives/PayrollInitiativeDto.cs @@ -0,0 +1,16 @@ +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities; +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Initiatives +{ + public class PayrollInitiativeDto : InitiativeDto + { + public Guid ConfigurationGuid { get; set; } + public PayrollGoalType PayrollGoal { get; set; } + public IEnumerable Departments { get; set; } + public IEnumerable JobCodes { get; set; } + public IEnumerable PayCodes { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/Filters/PRFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/Filters/PRFilterOptions.cs new file mode 100644 index 0000000..595d53b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/Filters/PRFilterOptions.cs @@ -0,0 +1,10 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Opportunities.Filters +{ + public class PRFilterOptions + { + public IEnumerable PayrollGoalTypes { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/Filters/PRFilters.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/Filters/PRFilters.cs new file mode 100644 index 0000000..ed58eb4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/Filters/PRFilters.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Opportunities.Filters +{ + public class PRFilters : FiltersOpportunity + { + public IEnumerable PayrollGoalTypes { get; set; } + + public PRFilters() : base() + { + PayrollGoalTypes = new List(); + } + + public override IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = base.GetFilters(context).ToList(); + + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + filters.Add(m => m.OpportunityKey.Contains(OpportunitySearch) + || m.Name.Contains(OpportunitySearch) + || m.PayrollGoal.Contains(OpportunitySearch)); + } + + if (PayrollGoalTypes.Any()) + { + filters.Add(m => PayrollGoalTypes.Contains(m.PayrollGoal)); + } + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/IPROpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/IPROpportunityService.cs new file mode 100644 index 0000000..1c235e3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/IPROpportunityService.cs @@ -0,0 +1,21 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities.Filters; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Opportunities +{ + public interface IPROpportunityService : IOpportunityService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + Task ExportAsync(SortOptions sortOptions, PRFilters filters, CancellationToken cancellationToken); + Task CreateAsync(PROpportunitySaveModel oppSaveModel, CancellationToken cancellationToken); + Task CreateAsync(PROpportunityDataModel oppDataModel, CancellationToken cancellationToken); + Task GetPROpportunityById(Guid oppSaveModel, CancellationToken cancellationToken); + Task GetInitiativeInfoAsync(Guid oppSaveModel, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunity.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunity.cs new file mode 100644 index 0000000..d5c7c56 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunity.cs @@ -0,0 +1,13 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Opportunities +{ + [Table("viewPayrollOpportunity", Schema = "cci")] + public class PROpportunity : Opportunity + { + public string Name { get; set; } + public long OpportunityId { get; set; } + public string PayrollGoal { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunityDataModel.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunityDataModel.cs new file mode 100644 index 0000000..747fbbc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunityDataModel.cs @@ -0,0 +1,69 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Mappers; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Opportunities +{ + [Table("PayrollOpportunity", Schema = "cci")] + public class PROpportunityDataModel : IOpportunity, IWithAuthor, IWithOwner + { + public PROpportunityDataModel() + { + OpportunityGuid = Guid.NewGuid(); + DateCreated = DateTime.Now; + DateModified = DateTime.Now; + } + + [Key] + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public string Note { get; set; } + public DateTime DateCreated { get; set; } + public DateTime DateModified { get; set; } + public PayrollGoalType PayrollGoal { get; set; } + public Guid OwnerGuid { get; set; } + public string OwnerName { get; set; } + public Guid AuthorGuid { get; set; } + public string AuthorName { get; set; } + public string DepartmentFilterJSON { get; set; } = "[]"; + public string JobCodeGroupFilterJSON { get; set; } = "[]"; + public string PayCodeGroupFilterJSON { get; set; } = "[]"; + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long OpportunityId { get; set; } + + [NotMapped] + public string OpportunityKey + { + get => $"PR - {OpportunityId}"; set + { // ILB + } + } + [NotMapped] + public double IdentifiedSavings { get; set; } + [NotMapped] + public bool HasInitiative => !InitiativeGuid.Equals(Guid.Empty); + [NotMapped] + public string Initiative { get; set; } + public bool IsHidden { get; set; } + [NotMapped] + public string SourceOpportunity { get; set; } + [NotMapped] + public long SourceOpportunityId { get; set; } + + public void Update(IOpportunity updates) + { + throw new NotImplementedException(); + } + } + + public enum PayrollGoalType : byte + { + Variable = 0, + Fixed = 1, + Both = 2 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunityDataModelMapper.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunityDataModelMapper.cs new file mode 100644 index 0000000..4f44ef3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunityDataModelMapper.cs @@ -0,0 +1,45 @@ +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Payroll.Initiatives; +using System.Security.Claims; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Opportunities +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class PROpportunityDataModelMapper + { + public static PROpportunityDataModel MapToDataModel(this PayrollInitiativeDto src, ClaimsPrincipal user) + { + var mapped = Mapped(src); + if (mapped == null) { return default; } + mapped.Note = ""; + mapped.MapAuthor(user); + mapped.MapOwner(user); + mapped.DepartmentFilterJSON = src.Departments.MapToHierarchyPaths(); + mapped.JobCodeGroupFilterJSON = src.JobCodes.MapToHierarchyPaths(); + mapped.PayCodeGroupFilterJSON = src.PayCodes.MapToHierarchyPaths(); + return mapped; + } + + public static PROpportunityDataModel MapToDataModel(this PROpportunitySaveModel src) + { + var mapped = Mapped(src); + if (mapped == null) return default; + mapped.DepartmentFilterJSON = src.Departments.MapToHierarchyPaths(); + mapped.JobCodeGroupFilterJSON = src.JobCodes.MapToHierarchyPaths(); + mapped.PayCodeGroupFilterJSON = src.PayCodes.MapToHierarchyPaths(); + return mapped; + } + + [MapperIgnoreTarget(nameof(PROpportunityDataModel.OpportunityGuid))] + private static partial PROpportunityDataModel Mapped(PROpportunitySaveModel src); + + [MapperIgnoreTarget(nameof(PROpportunityDataModel.OpportunityGuid))] + private static partial PROpportunityDataModel Mapped(PayrollInitiativeDto src); + + public static partial PROpportunityDto Mapped(this PROpportunity src); + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunitySaveModel.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunitySaveModel.cs new file mode 100644 index 0000000..6af809d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunitySaveModel.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Opportunities +{ + [ExcludeFromCodeCoverage] + public class PROpportunitySaveModel + { + [Required] + public Guid ConfigurationGuid { get; set; } + [Required] + [StringLength(200)] + public string Name { get; set; } + [Required] + public Guid OwnerGuid { get; set; } + public string OwnerName { get; set; } + public Guid AuthorGuid { get; set; } + public string AuthorName { get; set; } + public string Note { get; set; } + [Required] + public PayrollGoalType PayrollGoal { get; set; } + public List Departments { get; set; } + public List JobCodes { get; set; } + public List PayCodes { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunityService.cs new file mode 100644 index 0000000..38d53b9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Payroll/Opportunities/PROpportunityService.cs @@ -0,0 +1,209 @@ +using ClosedXML.Excel; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.Payroll.Opportunities.Filters; +using Strata.CoreLib.Claims; +using Strata.Hangfire.Jazz.Client; +using Strata.Hangfire.Jazz.Client.Models; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Payroll.Opportunities +{ + public class PROpportunityService : OpportunityService, IPROpportunityService + { + private readonly IJazzHangfireServiceClient _hangfireServiceClient; + private readonly IAuthorizationServiceClient _authorizationServiceClient; + private readonly IConfigurationService _configurationService; + + public PROpportunityService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IJazzHangfireServiceClient hangfireServiceClient, + IAuthorizationServiceClient authorizationServiceClient, + IInitiativeService initiativeService, + IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + IConfigurationService configurationService) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) + { + _hangfireServiceClient = hangfireServiceClient; + _authorizationServiceClient = authorizationServiceClient; + _configurationService = configurationService; + + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + if (dbContext == null) return null; + + var opportunityContext = dbContext.Set(); + var configuredOpportunities = opportunityContext.Where(en => en.ConfigurationGuid == configurationGuid); + + var payrollGoalTypes = await configuredOpportunities + .Select(opp => new FilterMember { Id = opp.PayrollGoal, Text = opp.PayrollGoal }).Distinct() + .OrderBy(opp => opp.Text).ToListAsync(cancellationToken); + + return new PRFilterOptions + { + PayrollGoalTypes = payrollGoalTypes + }; + } + + public async Task CreateAsync(PROpportunitySaveModel oppSaveModel, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + + // check for duplicate name + var nameExists = await dbContext.PayrollOpportunitiesDataModel.AnyAsync(x => x.Name == oppSaveModel.Name, cancellationToken); + if (nameExists) + { + throw new ArgumentException("Enter a unique name"); + } + + var opportunity = oppSaveModel.MapToDataModel(); + + // save off populated opportunity to database + await dbContext.PayrollOpportunitiesDataModel.AddAsync(opportunity); + var result = await dbContext.SaveChangesAsync(cancellationToken); + + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(opportunity.OpportunityGuid, SecurableEntityType.Opportunity, cancellationToken); + + // queue up a hangfire task to run payroll savings + if (result > 0) + { + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + var job = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.Payroll.RunPayrollSavingsTask, Strata.CS.Jazz.Biz.CCI", + ContextData = JsonConvert.SerializeObject(new { opportunityGuid = opportunity.OpportunityGuid, configurationGuid = opportunity.ConfigurationGuid }) + }; + + await _hangfireServiceClient.EnqueueJobAsync(job, cancellationToken); + } + else + { + throw new DbUpdateException("Error creating opportunity"); + } + + return opportunity.OpportunityGuid; + } + + public async Task CreateAsync(PROpportunityDataModel oppDataModel, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + + // save off populated opportunity to database + await dbContext.PayrollOpportunitiesDataModel.AddAsync(oppDataModel); + var result = await dbContext.SaveChangesAsync(cancellationToken); + + await _authorizationServiceClient.AddPermissionsFromDefaultsAsync(oppDataModel.OpportunityGuid, SecurableEntityType.Opportunity, cancellationToken); + + // queue up a hangfire task to run payroll savings + if (result > 0) + { + await _updateFactOpportunitySavingsService.UpdateFactOpportunitySavings(cancellationToken); + var job = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.Payroll.RunPayrollSavingsTask, Strata.CS.Jazz.Biz.CCI", + ContextData = JsonConvert.SerializeObject(new { opportunityGuid = oppDataModel.OpportunityGuid, configurationGuid = oppDataModel.ConfigurationGuid }) + }; + + var response = await _hangfireServiceClient.EnqueueJobAsync(job, cancellationToken); + return response.JobId; + } + else + { + throw new DbUpdateException("Error creating opportunity"); + } + } + + public async Task ExportAsync(SortOptions sortOptions, PRFilters filters, CancellationToken cancellationToken) + { + var exportOptions = this.GetExportOptions(); + + // get data + var opportunities = await this.GetAllAsync(sortOptions, filters, cancellationToken); + var data = opportunities.Select(opp => new + { + Opportunity = opp.OpportunityKey, + opp.Name, + opp.IdentifiedSavings, + opp.PayrollGoal, + opp.Initiative, + IsHidden = opp.IsHidden ? 1 : 0, + opp.Note + }); + + // configure column formats + exportOptions.AddColumnOptions("Identified Savings", "Currency2Dec"); + exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\""); + + return ExportUtils.CreateExcelWorkbook(data, exportOptions); + } + + public async Task GetPROpportunityById(Guid opportunityGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var opportunity = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .FirstOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + + if (opportunity == null) { return null; } + + if (opportunity is IWithSourceOpportunity && _centralDbContext != null) + { + try + { + var clientNetworkOpportunity = await _centralDbContext.ClientNetworkOpportunities + .Include(cno => cno.Opportunities) + .Include(cno => cno.NetworkOpportunity) + .SingleOrDefaultAsync(cno => cno.Opportunities.Any(link => link.OpportunityGuid == opportunity.OpportunityGuid)); + ((IWithSourceOpportunity)opportunity).SetSourceOpportunity(clientNetworkOpportunity); + } + catch + { + // Intentionally left blank + } + } + return opportunity; + } + + public async Task GetInitiativeInfoAsync(Guid opportunityGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) { return default; } + var opportunityItem = await dbContext.AddUserFilter(dbContext.Set().AsQueryable(), _user) + .SingleOrDefaultAsync(opp => opp.OpportunityGuid == opportunityGuid, cancellationToken); + if (opportunityItem == null) { return default; } + var configuration = await _configurationService.GetConfigurationAsync(opportunityItem.ConfigurationGuid, cancellationToken); + + if (opportunityItem.InitiativeGuid != Guid.Empty) + { + var initiative = dbContext?.Initiatives.SingleOrDefault(i => i.InitiativeGUID == opportunityItem.InitiativeGuid); + var fiscalMonths = configuration.GetTrackingFiscalMonths(OpportunityTypes.OrgDefined, initiative.EffectiveDate); + return initiative?.InitiativeInfo(opportunityItem.OpportunityGuid, fiscalMonths, configuration.OrgDefinedTrackingStartDate, configuration.OrgDefinedTrackingEndDate); + } + var info = new InitiativeInfo + { + OpportunityGuid = opportunityGuid, + Name = opportunityItem.Name, + CostDriver = "Payroll", + StartMonth = configuration.OrgDefinedTrackingStartDate.ToString("yyyy-MM-dd"), + EndMonth = configuration.OrgDefinedTrackingEndDate.ToString("yyyy-MM-dd") + }; + return info; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PeerGroups/Enums/PeerGroupCritetiaType.cs b/src/Strata.ContinuousImprovement.Biz/PeerGroups/Enums/PeerGroupCritetiaType.cs new file mode 100644 index 0000000..331a8ea --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PeerGroups/Enums/PeerGroupCritetiaType.cs @@ -0,0 +1,12 @@ +namespace Strata.ContinuousImprovement.Biz.PeerGroups.Enums +{ + public enum PeerGroupCritetiaType + { + EntityTypes, + CensusRegions, + OperatingExpenses, + AMCTypes, + UrbanRural, + BedSize + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PeerGroups/IPeerGroupService.cs b/src/Strata.ContinuousImprovement.Biz/PeerGroups/IPeerGroupService.cs new file mode 100644 index 0000000..19b11d5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PeerGroups/IPeerGroupService.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.PeerGroups +{ + public interface IPeerGroupService + { + public PeerGroupDropdownItems GetDropdownItems(); + public Task> GetAllAsync(CancellationToken cancellationToken); + public Task GetByIdAsync(int peerGroupId, CancellationToken cancellationToken); + public Task ContainsAsync(int peerGroupId, CancellationToken cancellationToken); + public Task CreateAsync(PeerGroupDto dto, CancellationToken cancellationToken); + public Task IsNameValid(string name, CancellationToken cancellationToken, int peerGroupId = 0); + public Task DeleteAsync(int peerGroupId, CancellationToken cancellationToken); + public Task UpdateAsync(int peerGroupId, PeerGroupDto saveInfo, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroup.cs b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroup.cs new file mode 100644 index 0000000..569d029 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroup.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.PeerGroups +{ + [Table("PeerGroup", Schema = "cci")] + public class PeerGroup + { + [Key] + public int PeerGroupId { get; set; } + public string Name { get; set; } + + public ICollection Mappings { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupCriteriaMapping.cs b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupCriteriaMapping.cs new file mode 100644 index 0000000..3519566 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupCriteriaMapping.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.PeerGroups +{ + [Table("PeerGroupCriteriaMapping", Schema = "cci")] + public class PeerGroupCriteriaMapping + { + [Key] + public int RowId { get; set; } + public int PeerGroupId { get; set; } + public byte MappingValue { get; set; } + public byte MappingType { get; set; } + + public PeerGroup PeerGroup { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupDropdownItems.cs b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupDropdownItems.cs new file mode 100644 index 0000000..ae2852d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupDropdownItems.cs @@ -0,0 +1,25 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.PeerGroups +{ + public class PeerGroupDropdownItems + { + public PeerGroupDropdownItems() + { + EntityTypeList = new List(); + CensusRegionList = new List(); + OperatingExpenseList = new List(); + AMCTypeList = new List(); + BedSizeList = new List(); + UrbanRuralList = new List(); + } + + public List EntityTypeList { get; set; } + public List CensusRegionList { get; set; } + public List OperatingExpenseList { get; set; } + public List AMCTypeList { get; set; } + public List BedSizeList { get; set; } + public List UrbanRuralList { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupDto.cs b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupDto.cs new file mode 100644 index 0000000..3ffa65d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupDto.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.PeerGroups +{ + public class PeerGroupDto + { + public int PeerGroupId { get; set; } + public string Name { get; set; } + public IEnumerable AmcTypes { get; set; } + public IEnumerable EntityTypes { get; set; } + public IEnumerable CensusRegions { get; set; } + public IEnumerable OperatingExpenses { get; set; } + public IEnumerable BedSize { get; set; } + public IEnumerable UrbanRural { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupMapper.cs b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupMapper.cs new file mode 100644 index 0000000..ea7a3e2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupMapper.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Logging; +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.PeerGroups.Enums; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.StrataSphereCompare.Client.Models.Enums; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.PeerGroups +{ + [Mapper()] + public static partial class PeerGroupMapper + { + public static PeerGroupDto MapToDto(this PeerGroup src, ILogger logger, IEnumerable mappings) + { + var dto = src.Map(); + + try + { + dto.EntityTypes = GetMappingValues(mappings.Where(m => m.MappingType == (byte)PeerGroupCritetiaType.EntityTypes)); + dto.UrbanRural = GetMappingValues(mappings.Where(m => m.MappingType == (byte)PeerGroupCritetiaType.UrbanRural)); + + if (mappings.FirstOrDefault(m => m.MappingType == (byte)PeerGroupCritetiaType.AMCTypes) != null) + { + dto.AmcTypes = mappings + .Where(m => m.MappingType == (byte)PeerGroupCritetiaType.AMCTypes).Count() == Enum.GetValues(typeof(IsAmc)).Length + ? GetMappingValues(mappings.Where(m => m.MappingType == (byte)PeerGroupCritetiaType.AMCTypes)) + : GetMappingValues(mappings.Where(m => m.MappingType == (byte)PeerGroupCritetiaType.AMCTypes)); + } + + dto.CensusRegions = GetMappingValues(mappings.Where(m => m.MappingType == (byte)PeerGroupCritetiaType.CensusRegions)); + dto.BedSize = GetMappingValues(mappings.Where(m => m.MappingType == (byte)PeerGroupCritetiaType.BedSize)); + dto.OperatingExpenses = GetMappingValues(mappings.Where(m => m.MappingType == (byte)PeerGroupCritetiaType.OperatingExpenses)); + } + catch (Exception ex) + { + logger.LogServiceError(ex, ex.Message, new Dictionary { { "PeerGroup", src } }); + } + return dto; + } + + private static IEnumerable GetMappingValues(IEnumerable dbMappings) + { + return dbMappings.Select(m => m.MappingValue); + } + + private static partial PeerGroupDto Map(this PeerGroup src); + public static partial PeerGroup Map(this PeerGroupDto src); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupService.cs b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupService.cs new file mode 100644 index 0000000..e3bdc8c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PeerGroups/PeerGroupService.cs @@ -0,0 +1,204 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.ContinuousImprovement.Biz.PeerGroups.Enums; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using Strata.StrataSphereCompare.Client.Models.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.PeerGroups +{ + public class PeerGroupService : IPeerGroupService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ILogger _logger; + + public PeerGroupService(IAsyncDbContextFactory dbContextFactory, ILogger logger) + { + _dbContextFactory = dbContextFactory; + _logger = logger; + } + + public async Task ContainsAsync(int peerGroupId, CancellationToken cancellationToken) + { + await using var dbContext = await GetDbContext(cancellationToken); + return await dbContext.PeerGroups + .AnyAsync(x => x.PeerGroupId == peerGroupId, cancellationToken); + } + + public async Task> GetAllAsync(CancellationToken cancellationToken) + { + await using var dbContext = await GetDbContext(cancellationToken); + var peerGroups = await dbContext.PeerGroups.Include(pg => pg.Mappings).ToListAsync(); + return peerGroups.Select(group => group.MapToDto(_logger, group.Mappings)); + } + + public async Task GetByIdAsync(int peerGroupId, CancellationToken cancellationToken) + { + await using var dbContext = await GetDbContext(cancellationToken); + var peerGroup = await dbContext.PeerGroups.Include(pg => pg.Mappings).SingleOrDefaultAsync(irc => irc.PeerGroupId == peerGroupId, cancellationToken); + return peerGroup.MapToDto(_logger, peerGroup.Mappings); + } + + public PeerGroupDropdownItems GetDropdownItems() + { + var dropdownItems = new PeerGroupDropdownItems(); + + GetDropdownList(dropdownItems.EntityTypeList); + dropdownItems.EntityTypeList = dropdownItems.EntityTypeList.OrderBy(et => et.Text).ToList(); + + GetDropdownList(dropdownItems.CensusRegionList); + dropdownItems.CensusRegionList = dropdownItems.CensusRegionList.OrderBy(et => et.Text).ToList(); + + GetDropdownList(dropdownItems.OperatingExpenseList); + + GetDropdownList(dropdownItems.BedSizeList); + + foreach (IsAmc amcType in Enum.GetValues(typeof(IsAmc))) + { + dropdownItems.AMCTypeList.Add(new PickerItem() { Value = (byte)amcType, Text = (byte)amcType != 0 ? "Yes" : "No" }); + } + dropdownItems.AMCTypeList = dropdownItems.AMCTypeList.OrderBy(et => et.Text).ToList(); + + foreach (UrbanRural urbRur in Enum.GetValues(typeof(UrbanRural))) + { + dropdownItems.UrbanRuralList.Add(new PickerItem() { Value = (byte)urbRur, Text = (byte)urbRur != 0 ? "Rural" : "Urban" }); + } + dropdownItems.UrbanRuralList = dropdownItems.UrbanRuralList.OrderBy(et => et.Text).ToList(); + + return dropdownItems; + } + + public async Task CreateAsync(PeerGroupDto dto, CancellationToken cancellationToken) + { + var errorMessage = await ValidateName(0, dto, cancellationToken); + if (errorMessage != string.Empty) + { + throw new Exception(errorMessage); + } + await using var dbContext = await GetDbContext(cancellationToken); + var peerGroup = dto.Map(); + peerGroup.Mappings = GetMappings(dto, peerGroup.PeerGroupId).ToList(); + + await dbContext.PeerGroups.AddAsync(peerGroup, cancellationToken); + var isAdded = await dbContext.SaveChangesAsync(cancellationToken) > 0; + + return isAdded; + } + + public async Task IsNameValid(string name, CancellationToken cancellationToken, int peerGroupId = 0) + { + await using var dbContext = await GetDbContext(cancellationToken); + return !await dbContext.PeerGroups.AnyAsync(x => x.PeerGroupId != peerGroupId && x.Name.ToLower() == name.ToLower(), cancellationToken); + } + + public async Task DeleteAsync(int peerGroupId, CancellationToken cancellationToken) + { + await using var dbContext = await GetDbContext(cancellationToken); + using var transaction = dbContext.Database.BeginTransaction(); + try + { + var peerGroup = await dbContext.PeerGroups.Include(pg => pg.Mappings) + .SingleOrDefaultAsync(irc => irc.PeerGroupId == peerGroupId, cancellationToken); + + dbContext.PeerGroupCriteriaMappings.RemoveRange(peerGroup.Mappings); + await dbContext.SaveChangesAsync(cancellationToken); + + dbContext.PeerGroups.Remove(peerGroup); + await dbContext.SaveChangesAsync(cancellationToken); + transaction.Commit(); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + transaction.Rollback(); + throw; + } + } + + public async Task UpdateAsync(int peerGroupId, PeerGroupDto saveInfo, CancellationToken cancellationToken) + { + var errorMessage = await ValidateName(peerGroupId, saveInfo, cancellationToken); + if (errorMessage != string.Empty) + { + throw new Exception(errorMessage); + } + await using var dbContext = await GetDbContext(cancellationToken); + using var transaction = dbContext.Database.BeginTransaction(); + try + { + + var peerGroup = await dbContext.PeerGroups.Include(pg => pg.Mappings) + .SingleOrDefaultAsync(irc => irc.PeerGroupId == peerGroupId, cancellationToken); + if (peerGroup == null) return false; + + peerGroup.Name = saveInfo.Name; + dbContext.PeerGroups.Update(peerGroup); + dbContext.PeerGroupCriteriaMappings.RemoveRange(peerGroup.Mappings); + var newMappings = GetMappings(saveInfo, peerGroupId); + dbContext.PeerGroupCriteriaMappings.AddRange(newMappings); + await dbContext.SaveChangesAsync(cancellationToken); + transaction.Commit(); + + return true; + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + transaction.Rollback(); + throw; + } + } + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + private IEnumerable GetMappings(PeerGroupDto dto, int peerGroupId) + { + var mappings = dto.EntityTypes.Select(et => + new PeerGroupCriteriaMapping() { MappingValue = et, MappingType = (byte)PeerGroupCritetiaType.EntityTypes, PeerGroupId = peerGroupId }).ToList(); + mappings.AddRange((dto.UrbanRural ?? Enumerable.Empty()).Select(et => + new PeerGroupCriteriaMapping() { MappingValue = et, MappingType = (byte)PeerGroupCritetiaType.UrbanRural, PeerGroupId = peerGroupId })); + mappings.AddRange(dto.AmcTypes.Select(et => + new PeerGroupCriteriaMapping() { MappingValue = et, MappingType = (byte)PeerGroupCritetiaType.AMCTypes, PeerGroupId = peerGroupId })); + mappings.AddRange(dto.CensusRegions.Select(et => + new PeerGroupCriteriaMapping() { MappingValue = et, MappingType = (byte)PeerGroupCritetiaType.CensusRegions, PeerGroupId = peerGroupId })); + mappings.AddRange((dto.BedSize ?? Enumerable.Empty()).Select(et => + new PeerGroupCriteriaMapping() { MappingValue = et, MappingType = (byte)PeerGroupCritetiaType.BedSize, PeerGroupId = peerGroupId })); + mappings.AddRange(dto.OperatingExpenses.Select(et => + new PeerGroupCriteriaMapping() { MappingValue = et, MappingType = (byte)PeerGroupCritetiaType.OperatingExpenses, PeerGroupId = peerGroupId })); + return mappings; + } + + private async Task ValidateName(int peerGroupId, PeerGroupDto dto, CancellationToken cancellationToken) + { + if (!await IsNameValid(dto.Name, cancellationToken, peerGroupId)) + { + return "Rollup column name already exists."; + } + + if (dto.Name.Length > 100) + { + return "Rollup column exceeds maximum number of characters"; + } + + return string.Empty; + } + + private void GetDropdownList(List items) where T : Enum + { + foreach (var entityType in Enum.GetValues(typeof(T))) + { + items.Add(new PickerItem() { Value = (int)entityType, Text = ((T)entityType).GetDescription() }); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceBaselineData.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceBaselineData.cs new file mode 100644 index 0000000..4f6782f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceBaselineData.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class PerformanceBaselineData + { + + internal double BaselineUnits { get; set; } + internal double BaselineStats { get; set; } + internal int FiscalYearId { get; set; } + internal int FiscalMonthId { get; set; } + + public PerformanceBaselineData(double baselineUnits, double baselineStats, int fiscalYearId, int fiscalMonthId) + { + BaselineUnits = baselineUnits; + BaselineStats = baselineStats; + FiscalYearId = fiscalYearId; + FiscalMonthId = fiscalMonthId; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceComparisonData.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceComparisonData.cs new file mode 100644 index 0000000..ea5d563 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceComparisonData.cs @@ -0,0 +1,81 @@ +using DocumentFormat.OpenXml.Drawing.Diagrams; +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class PerformanceComparisonData + { + internal double GoalUnits { get; set; } + internal double BaselineUnits { get; set; } + internal double BaselineStats { get; set; } + internal double TrackingUnits { get; set; } + internal double TrackingStats { get; set; } + internal double GoalUnitsOverall { get; set; } + internal double BaselineUnitsOverall { get; set; } + internal double TrackingUnitsOverall { get; set; } + internal int FiscalMonthId { get; set; } + internal int FiscalYearId { get; set; } + +#pragma warning disable S107 // Methods should not have too many parameters + public PerformanceComparisonData(int fiscalMonthID, int fiscalYearId, double goalUnits, double baselineUnits, double baselineStats, double trackingUnits, double trackingStats, + double goalUnitsOverall, double baselineUnitsOverall, double trackingUnitsOverall) +#pragma warning restore S107 // Methods should not have too many parameters + { + FiscalMonthId = fiscalMonthID; + FiscalYearId = fiscalYearId; + GoalUnits = goalUnits; + BaselineUnits = baselineUnits; + BaselineStats = baselineStats; + TrackingUnits = trackingUnits; + TrackingStats = trackingStats; + GoalUnitsOverall = goalUnitsOverall; + BaselineUnitsOverall = baselineUnitsOverall; + TrackingUnitsOverall = trackingUnitsOverall; + } + + public static List SetupBaselineDataList(double[] baselineUnits, double[] baselineStats, int fiscalYearId, int startMonthId = 1) + { + if (baselineUnits.Length != baselineStats.Length) throw new ArgumentException("All arrays should have the same length."); + + var dataList = new List(); + var iterator = 0; + + while (iterator < baselineUnits.Length) + { + var data = new PerformanceBaselineData( baselineUnits[iterator], baselineStats[iterator], fiscalYearId, startMonthId); + dataList.Add(data); + if (startMonthId == 12) + { + startMonthId = 0; + fiscalYearId++; + } + else + { + startMonthId++; + } + iterator++; + } + + return dataList; + } + + public static List SetupTrackingDataList(double[] goalUnits, double[] trackingUnits, double[] trackingStats, int fiscalYearId) + { + if (goalUnits.Length != trackingUnits.Length || trackingUnits.Length != trackingStats.Length) throw new ArgumentException("All arrays should have the same length."); + + var dataList = new List(); + + for (int i = 0; i < trackingUnits.Length; i++) + { + var data = new PerformanceTrackingData(goalUnits[i], trackingUnits[i], trackingStats[i], fiscalYearId, i + 1); + dataList.Add(data); + } + + return dataList; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceGroup.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceGroup.cs new file mode 100644 index 0000000..dda3aa5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceGroup.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class PerformanceGroup + { + internal int GroupId { get; set; } + internal string GroupName { get; set; } + + public PerformanceGroup(int GroupId, string GroupName) + { + this.GroupId = GroupId; + this.GroupName = GroupName; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceMonthlySavingsData.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceMonthlySavingsData.cs new file mode 100644 index 0000000..1d56a5a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceMonthlySavingsData.cs @@ -0,0 +1,14 @@ +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class PerformanceMonthlySavingsData : PerformanceMonthlyUnitData + { + public double Savings { get; set; } + public double SavingsYtd { get; set; } + + public PerformanceMonthlySavingsData(int fiscalMonthId, int fiscalYearId, double rampUp, double units, double stats, double savings, bool isInverse) + : base(fiscalMonthId, fiscalYearId, rampUp, units, stats) + { + Savings = savings * (isInverse ? -1 : 1); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceMonthlyUnitData.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceMonthlyUnitData.cs new file mode 100644 index 0000000..43acf13 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceMonthlyUnitData.cs @@ -0,0 +1,47 @@ +using Strata.ContinuousImprovement.Biz.Utilities; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + /// + /// A single monthly set of Performance data + /// + public class PerformanceMonthlyUnitData + { + public int FiscalMonthId { get; set; } + public int FiscalYearId { get; set; } + public double RampUp { get; set; } + public double Units { get; set; } + public double UnitsYtd { get; set; } + public double Stats { get; set; } + public double StatsYtd { get; set; } + + /// + /// Units DivideBy Stats + /// + public double UnitsPerStat => Units.DivideBy(Stats); + public double UnitsPerStatYtd => UnitsYtd.DivideBy(StatsYtd); + + public double UnitsRampUp => Units * RampUp; + public double StatsRampUp => Stats * RampUp; + + public double UnitsPerStatRampUp => UnitsPerStat * RampUp; + public double UnitsPerStatRampUpYtd => UnitsPerStatYtd * RampUp; + + /// + /// A single monthly set of Performance data + /// + /// The Fiscal Month Id of the data unit + /// /// The Fiscal Year Id of the data unit + /// The RampUp value of the data unit, 0 forces the Units to be 0 + /// The Units value of the data unit being measured + /// The Stats value of data unit + public PerformanceMonthlyUnitData(int fiscalMonthId, int fiscalYearId, double rampUp, double units, double stats) + { + FiscalMonthId = fiscalMonthId; + FiscalYearId = fiscalYearId; + RampUp = rampUp; + Units = rampUp == 0 ? 0 : units; + Stats = stats; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceOptions.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceOptions.cs new file mode 100644 index 0000000..128f78a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceOptions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class PerformanceOptions + { + internal bool IsSeasonal { get; set; } + internal bool IsInverse { get; set; } + internal bool HasStatistic { get; set; } + + public PerformanceOptions(bool isSeasonal, bool isInverse, bool hasStatistic) + { + IsSeasonal = isSeasonal; + IsInverse = isInverse; + HasStatistic = hasStatistic; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceStatistics.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceStatistics.cs new file mode 100644 index 0000000..f02c8fd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceStatistics.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class PerformanceStatistics + { + internal double BaselineStats { get; set; } + internal double BaselineStatsOverall { get; set; } + internal double TrackingStats { get; set; } + internal double TrackingStatsOverall { get; set; } + + public PerformanceStatistics(double baselineStats, double baselineStatsOverall, double trackingStats, double trackingStatsOverall) + { + BaselineStats = baselineStats; + BaselineStatsOverall = baselineStatsOverall; + TrackingStats = trackingStats; + TrackingStatsOverall = trackingStatsOverall; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceTrackingData.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceTrackingData.cs new file mode 100644 index 0000000..3b22bd0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceTrackingData.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class PerformanceTrackingData + { + internal double GoalUnits { get; set; } + internal double TrackingUnits { get; set; } + internal double TrackingStats { get; set; } + internal int FiscalYearId { get; set; } + internal int FiscalMonthId { get; set; } + + public PerformanceTrackingData(double goalUnits, double trackingUnits, double trackingStats, int fiscalYearId, int fiscalMonthId) + { + GoalUnits = goalUnits; + TrackingUnits = trackingUnits; + TrackingStats = trackingStats; + FiscalYearId = fiscalYearId; + FiscalMonthId = fiscalMonthId; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceTrackingItem.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceTrackingItem.cs new file mode 100644 index 0000000..8033a4d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceTrackingItem.cs @@ -0,0 +1,124 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class PerformanceTrackingItem + { + private readonly IList _monthlyTrackingData; + + public int GroupId { get; set; } + public string GroupName { get; set; } + + public IEnumerable MonthlyTrackingData => _monthlyTrackingData; + + public PerformanceTrackingItem(PerformanceGroup group, IList rampUps, PerformanceOptions options, + IList baselineData, IList trackingData, IList trackingYearMonths) + { + GroupId = group.GroupId; + GroupName = group.GroupName; + + trackingYearMonths = trackingYearMonths.OrderBy(t => t.FiscalYearID).ThenBy(t => t.FiscalMonth.SortOrder).ToList(); + + var performanceComparisons = GetPerformanceComparisonData(baselineData, trackingData); + + var baselineStatsOverall = options.HasStatistic ? baselineData.Sum(d => d.BaselineStats) : baselineData.Count(); + var trackingStatsOverall = options.HasStatistic ? trackingData.Sum(d => d.TrackingStats) : trackingYearMonths.Count(m => m.IsTrackedMonth); + + _monthlyTrackingData = new List(); + foreach (var month in trackingYearMonths) + { + var trackingComparison = performanceComparisons.First(t => t.FiscalMonthId == month.FiscalMonthID && t.FiscalYearId == month.FiscalYearID); + var rampUp = rampUps.First(p => p.FiscalMonthId == month.FiscalMonthID && p.FiscalYearId == month.FiscalYearID); + + var trackedMonth = month.IsTrackedMonth ? 1 : 0; + var baselineStats = options.HasStatistic ? trackingComparison.BaselineStats : 1; + var trackingStats = options.HasStatistic ? trackingComparison.TrackingStats : trackedMonth; + + var statistics = new PerformanceStatistics(baselineStats, baselineStatsOverall, trackingStats, trackingStatsOverall); + + var monthlyTrackingData = new PerformanceTrackingMonth(month, rampUp.ValuePercentage, trackingComparison, statistics, _monthlyTrackingData, options, baselineData.Count, trackingData.Count); + + _monthlyTrackingData.Add(monthlyTrackingData); + } + } + + private static List GetPerformanceComparisonData(IList baselineData, IList trackingData) + { + var performanceComparisonData = new List(); + var baselineUnitsTotal = baselineData.Sum(b => b.BaselineUnits); + var baselineGoalUnitsTotal = trackingData.Sum(b => b.GoalUnits); + var trackingUnitsTotal = trackingData.Sum(b => b.TrackingUnits); + + foreach (var trackingMonth in trackingData) + { + var baselineMatch = baselineData.Where(ym => ym.FiscalMonthId == trackingMonth.FiscalMonthId); + switch (baselineMatch.Count()) + { + //No matching month in the baseline - compare to full baseline average + case 0: + performanceComparisonData.Add(new PerformanceComparisonData(trackingMonth.FiscalMonthId, trackingMonth.FiscalYearId, trackingData.Average(d => d.GoalUnits), baselineData.Average(d => d.BaselineUnits), baselineData.Average(d => d.BaselineStats), trackingMonth.TrackingUnits, trackingMonth.TrackingStats, baselineGoalUnitsTotal, baselineUnitsTotal, trackingUnitsTotal)); + break; + //One matching month in the baseline - compare directly + case 1: + var baselineMonth = baselineMatch.First(); + performanceComparisonData.Add(new PerformanceComparisonData(trackingMonth.FiscalMonthId, trackingMonth.FiscalYearId, trackingMonth.GoalUnits, baselineMonth.BaselineUnits, baselineMonth.BaselineStats, trackingMonth.TrackingUnits, trackingMonth.TrackingStats, baselineGoalUnitsTotal, baselineUnitsTotal, trackingUnitsTotal)); + break; + //More than one matching month in the baseline - compare to average of matches + default: + performanceComparisonData.Add(new PerformanceComparisonData(trackingMonth.FiscalMonthId, trackingMonth.FiscalYearId, trackingData.Average(d => d.GoalUnits), baselineMatch.Average(d => d.BaselineUnits), baselineMatch.Average(d => d.BaselineStats), trackingMonth.TrackingUnits, trackingMonth.TrackingStats, baselineGoalUnitsTotal, baselineUnitsTotal, trackingUnitsTotal)); + break; + } + } + return performanceComparisonData; + } + + + public double AnnualGoal => _monthlyTrackingData + .Sum(p => double.IsNaN(p.CommittedSavings.Savings) ? 0 : p.CommittedSavings.Savings); + + public double AnnualProjectActual => _monthlyTrackingData + .Sum(p => double.IsNaN(p.CommittedUnits) ? 0 : p.CommittedUnits); + + public double YTDGoal => _monthlyTrackingData + .Where(p => p.Month.IsCurrentMonth) + .Sum(p => double.IsNaN(p.CommittedSavings.SavingsYtd) ? 0 : p.CommittedSavings.SavingsYtd); + + public double YTDActual => _monthlyTrackingData + .Where(p => p.Month.IsCurrentMonth) + .Sum(p => double.IsNaN(p.ActualSavings.SavingsYtd) ? 0 : p.ActualSavings.SavingsYtd); + + public double CurrentMonthGoal => _monthlyTrackingData + .Where(p => p.Month.IsCurrentMonth) + .Sum(p => double.IsNaN(p.CommittedSavings.Savings) ? 0 : p.CommittedSavings.Savings); + + public double CurrentMonthActual => _monthlyTrackingData + .Where(p => p.Month.IsCurrentMonth) + .Sum(p => double.IsNaN(p.ActualSavings.Savings) ? 0 : p.ActualSavings.Savings); + + public double ActualSavings(int elementAt) => + double.IsNaN(_monthlyTrackingData.ElementAt(elementAt).ActualSavings.Savings) || !IsAfterEffectiveDate(elementAt) + ? 0.0 + : _monthlyTrackingData.ElementAt(elementAt).ActualSavings.Savings; + + public double ActualSavingsYTD(int elementAt) => + double.IsNaN(_monthlyTrackingData.ElementAt(elementAt).ActualSavings.SavingsYtd) || !IsAfterEffectiveDate(elementAt) + ? 0.0 + : _monthlyTrackingData.ElementAt(elementAt).ActualSavings.SavingsYtd; + + public double CommittedSavings(int elementAt) => + double.IsNaN(_monthlyTrackingData.ElementAt(elementAt).CommittedSavings.Savings) || !IsAfterEffectiveDate(elementAt) + ? 0.0 + : _monthlyTrackingData.ElementAt(elementAt).CommittedSavings.Savings; + + public double CommittedSavingsYTD(int elementAt) => + double.IsNaN(_monthlyTrackingData.ElementAt(elementAt).CommittedSavings.SavingsYtd) || !IsAfterEffectiveDate(elementAt) + ? 0.0 + : _monthlyTrackingData.ElementAt(elementAt).CommittedSavings.SavingsYtd; + + public bool IsAfterEffectiveDate(int elementAt) => _monthlyTrackingData.ElementAt(elementAt).Month.IsAfterEffectiveDate; + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceTrackingMonth.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceTrackingMonth.cs new file mode 100644 index 0000000..8f921d1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/PerformanceTrackingMonth.cs @@ -0,0 +1,104 @@ +using MoreLinq; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.Utilities; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class PerformanceTrackingMonth + { + public TrackingYearMonth Month { get; set; } + public int TrackingMonthsCount { get; set; } + public int BaselineMonthsCount { get; set; } + public double RampUp { get; set; } + public double GoalUnits { get; set; } + public double GoalUnitsYtd { get; set; } + public double CommittedUnits => IsSeasonal ? BaselineMonthlyUnitData.Units + GoalUnitVariance * RampUp + : (BaselineUnitsOverall / BaselineMonthsCount) + (GoalUnitVariance * RampUp); + public double CommittedUnitsYtd { get; set; } + public double GoalUnitsOverall { get; set; } + public double BaselineUnitsOverall { get; set; } + // units per statistic per month vs. units per month. + public double BaselineAverage => HasStatistic ? BaselineUnitsOverall.DivideBy(BaselineStatsOverall) : BaselineUnitsOverall / BaselineMonthsCount; + public double BaselineStatsOverall { get; set; } + public double TrackingUnitsOverall { get; set; } + public double TrackingStatsOverall { get; set; } + public bool HasStatistic { get; set; } + public bool IsSeasonal { get; set; } + public bool IsInverse { get; set; } + public PerformanceMonthlyUnitData BaselineMonthlyUnitData { get; set; } + public PerformanceMonthlyUnitData TrackingMonthlyUnitData { get; set; } + public PerformanceMonthlySavingsData GoalSavings { get; set; } + public PerformanceMonthlySavingsData CommittedSavings { get; set; } + public PerformanceMonthlySavingsData ActualSavings { get; set; } + public double GoalUnitVariance => IsSeasonal ? GoalUnits - BaselineMonthlyUnitData.Units + : (GoalUnitsOverall / TrackingMonthsCount) - (BaselineUnitsOverall / BaselineMonthsCount); + public double ActualUnitVariance + { + get + { + if (RampUp.Equals(0) || BaselineMonthlyUnitData.Stats.Equals(0)) return 0; + return IsSeasonal ? (TrackingMonthlyUnitData.UnitsPerStat - BaselineMonthlyUnitData.UnitsPerStat) * TrackingMonthlyUnitData.Stats + : (TrackingMonthlyUnitData.UnitsPerStat - BaselineAverage) * TrackingMonthlyUnitData.Stats; + } + } + + public double CommittedUnitVariance => GoalUnitVariance * RampUp; + public double CommittedStats => IsSeasonal ? BaselineMonthlyUnitData.Stats + : BaselineStatsOverall / BaselineMonthsCount; + + public double CommittedRate => CommittedSavings.UnitsPerStat; + + public PerformanceTrackingMonth(TrackingYearMonth month, double rampUp, PerformanceComparisonData comparisonData, PerformanceStatistics statistics, IList previousTrackingMonths, PerformanceOptions options, int baselineMonthsCount, int trackingMonthsCount) + { + BaselineMonthsCount = baselineMonthsCount; + TrackingMonthsCount = trackingMonthsCount; + Month = month; + RampUp = rampUp; + GoalUnits = comparisonData.GoalUnits; + GoalUnitsOverall = comparisonData.GoalUnitsOverall; + BaselineUnitsOverall = comparisonData.BaselineUnitsOverall; + BaselineStatsOverall = statistics.BaselineStatsOverall; + TrackingUnitsOverall = comparisonData.TrackingUnitsOverall; + TrackingStatsOverall = statistics.TrackingStatsOverall; + HasStatistic = options.HasStatistic; + IsSeasonal = options.IsSeasonal; + IsInverse = options.IsInverse; + BaselineMonthlyUnitData = new PerformanceMonthlyUnitData(month.FiscalMonthID, month.FiscalYearID, 1, comparisonData.BaselineUnits, statistics.BaselineStats); + TrackingMonthlyUnitData = new PerformanceMonthlyUnitData(month.FiscalMonthID, month.FiscalYearID, rampUp, comparisonData.TrackingUnits, statistics.TrackingStats); + GoalSavings = new PerformanceMonthlySavingsData(month.FiscalMonthID, month.FiscalYearID, 1, comparisonData.GoalUnits, statistics.BaselineStats, GoalUnitVariance, IsInverse); + CommittedSavings = new PerformanceMonthlySavingsData(month.FiscalMonthID, month.FiscalYearID, rampUp, CommittedUnits, CommittedStats, CommittedUnitVariance, IsInverse); + + ActualSavings = month.IsTrackedMonth + ? new PerformanceMonthlySavingsData(month.FiscalMonthID, month.FiscalYearID, rampUp, comparisonData.TrackingUnits, statistics.TrackingStats, ActualUnitVariance, IsInverse) + : new PerformanceMonthlySavingsData(month.FiscalMonthID, month.FiscalYearID, rampUp, 0, 0, 0, IsInverse); + + CalculateYTDTracking(previousTrackingMonths.Concat(new[] { this }).ToList()); + + } + + private void CalculateYTDTracking(IList trackingMonths) + { + trackingMonths.Where(tm => tm.Month.IsAfterEffectiveDate) + .ForEach(month => + { + BaselineMonthlyUnitData.UnitsYtd += month.BaselineMonthlyUnitData.Units; + BaselineMonthlyUnitData.StatsYtd += month.BaselineMonthlyUnitData.Stats; + + GoalUnitsYtd += month.GoalUnits; + CommittedUnitsYtd += month.CommittedUnits; + + CommittedSavings.UnitsYtd += month.CommittedSavings.Units; + CommittedSavings.StatsYtd += month.CommittedSavings.Stats; + + TrackingMonthlyUnitData.UnitsYtd += month.TrackingMonthlyUnitData.Units; + TrackingMonthlyUnitData.StatsYtd += month.TrackingMonthlyUnitData.Stats; + + GoalSavings.SavingsYtd += month.GoalSavings.Savings; + CommittedSavings.SavingsYtd += month.CommittedSavings.Savings; + ActualSavings.SavingsYtd += month.ActualSavings.Savings; + }); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/YearMonthContractResolver.cs b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/YearMonthContractResolver.cs new file mode 100644 index 0000000..c0463ea --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PerformanceEngine/YearMonthContractResolver.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Strata.ContinuousImprovement.Biz.Shared; + +namespace Strata.ContinuousImprovement.Biz.PerformanceEngine +{ + public class YearMonthContractResolver : DefaultContractResolver + { + public new static readonly YearMonthContractResolver Instance = new YearMonthContractResolver(); + + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (property.DeclaringType == typeof(YearMonth) && property.PropertyName == "FiscalMonth") + { + property.Ignored = true; + } + + return property; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/PickerItems/IPickerItemService.cs b/src/Strata.ContinuousImprovement.Biz/PickerItems/IPickerItemService.cs new file mode 100644 index 0000000..93151db --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PickerItems/IPickerItemService.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.PickerItems +{ + public interface IPickerItemService + { + Task> GetItems(Func selector, CancellationToken cancellationToken) where T : class, IWithStrataId; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/PickerItems/PickerItemService.cs b/src/Strata.ContinuousImprovement.Biz/PickerItems/PickerItemService.cs new file mode 100644 index 0000000..2904ba9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/PickerItems/PickerItemService.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.PickerItems +{ + internal class PickerItemService : IPickerItemService + { + private readonly CentralDbContext _centralDbContext; + private readonly ClaimsPrincipal _user; + public PickerItemService(CentralDbContext centralDbContext, IClaimsPrincipalAccessor claimsPrincipalAccessor) + { + _centralDbContext = centralDbContext; + _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + } + + public async Task> GetItems(Func selector, CancellationToken cancellationToken) where T : class, IWithStrataId + { + var pickerItems = await _centralDbContext.Set().Where(item => item.StrataId == _user.GetStrataId()) + .Select(item => selector(item)).ToListAsync(cancellationToken); + return pickerItems; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Population/ExplorationInclusionExclusion.cs b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationInclusionExclusion.cs new file mode 100644 index 0000000..39b14a8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationInclusionExclusion.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Population +{ + [Table("exploration_inclusion_exclusions")] + public class ExplorationInclusionExclusion + { + public long ExplorationPopulationId { get; set; } + public long EncounterId { get; set; } + public bool IsExcluded { get; set; } + public int StrataId { get; set; } + public ExplorationPopulation ExplorationPopulation { get; set; } + public ExplorationInclusionExclusion() { } + + public ExplorationInclusionExclusion(long explorationPopulationId, long encounterId, bool isExcluded) + { + ExplorationPopulationId = explorationPopulationId; + EncounterId = encounterId; + IsExcluded = isExcluded; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulation.cs b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulation.cs new file mode 100644 index 0000000..ea019a2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulation.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Replication.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Population +{ + [Table("exploration_populations")] + public class ExplorationPopulation : IPostgresReplicable, IWithStrataId + { + [Key] + public long ExplorationPopulationId { get; set; } + public string Name { get; set; } + public int CaseTypeFamilyId { get; set; } + public string CostDriver { get; set; } + public short LosExclusionType { get; set; } + public decimal LosExclusionValue { get; set; } + public short CostExclusionType { get; set; } + public decimal CostExclusionValue { get; set; } + public string FiltersJSON { get; set; } + public int TotalEncounters { get; set; } + public decimal TotalVariation { get; set; } + public short CodeType { get; set; } + public int StrataId { get; set; } + + public ICollection ExplorationPopulationEncounters { get; set; } + public ICollection ExplorationInclusionExclusions { get; set; } + + [NotMapped] + public List FilterChipItems => JsonConvert.DeserializeObject>(this.FiltersJSON); + + public (DateTime, DateTime) GetDischargeDates() + { + var dateFilter = FilterChipItems?.FirstOrDefault(f => f.ChipType == ChipType.DateRange && f.Key == ChipKey.DischargeDateID); + return dateFilter != null ? (dateFilter.DateRange.FirstOrDefault(), dateFilter.DateRange.LastOrDefault()) : (default, default); + } + + public DimExplorationPopulation MapToSnowflakeModel() + { + return this.MapToDimExplorationPopulation(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationEncounter.cs b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationEncounter.cs new file mode 100644 index 0000000..e775a46 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationEncounter.cs @@ -0,0 +1,27 @@ +using Strata.ContinuousImprovement.Biz.Replication.Models; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Population +{ + [Table("exploration_population_encounters")] + public class ExplorationPopulationEncounter : IPostgresReplicable + { + public long ExplorationPopulationId { get; set; } + public long EncounterId { get; set; } + public int StrataId { get; set; } + public ExplorationPopulation ExplorationPopulation { get; set; } + public ExplorationPopulationEncounter() { } + + public ExplorationPopulationEncounter(long explorationPopulationId, long encounterId) + { + ExplorationPopulationId = explorationPopulationId; + EncounterId = encounterId; + } + + public FactExplorationPopulationEncounter MapToSnowflakeModel() + { + return this.MapToFactExplorationPopulationEncounter(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationMapper.cs b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationMapper.cs new file mode 100644 index 0000000..b385630 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationMapper.cs @@ -0,0 +1,32 @@ +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Replication.Models; +using Strata.DecisionSupport.Models.Jazz; + +namespace Strata.ContinuousImprovement.Biz.Population +{ + [Mapper(UseDeepCloning = true, EnumMappingStrategy = EnumMappingStrategy.ByName, EnumMappingIgnoreCase = true)] + public static partial class ExplorationPopulationMapper + { + public static ExplorationPopulationDto MapToExplorationPopulationDto(this ExplorationPopulation src) + { + var mapped = src.Mapped(); + if (mapped == null) return default; + + return mapped; + } + + [MapperIgnoreSource(nameof(ExplorationPopulation.FilterChipItems))] + public static partial ExplorationPopulationInfo MapToExplorationPopulationInfo(this ExplorationPopulation src); + + [MapperIgnoreSource(nameof(ExplorationPopulation.ExplorationPopulationEncounters))] + [MapperIgnoreSource(nameof(ExplorationPopulation.ExplorationInclusionExclusions))] + [MapperIgnoreTarget(nameof(ExplorationPopulationDto.ExplorationPopulationEncounters))] + [MapperIgnoreTarget(nameof(ExplorationPopulationDto.ExplorationInclusionExclusions))] + private static partial ExplorationPopulationDto Mapped(this ExplorationPopulation src); + + public static partial DimExplorationPopulation MapToDimExplorationPopulation(this ExplorationPopulation src); + + public static partial FactExplorationPopulationEncounter MapToFactExplorationPopulationEncounter(this ExplorationPopulationEncounter src); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationReplicationJob.cs b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationReplicationJob.cs new file mode 100644 index 0000000..bfaed87 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationReplicationJob.cs @@ -0,0 +1,58 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Replication; +using Strata.ContinuousImprovement.Biz.Replication.Models; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Population +{ + internal class ExplorationPopulationReplicationJob + { + private const string EXPLORATION_POPULATION_TABLE = "cci.DimExplorationPopulation"; + private const string EXPLORATION_POPULATION_ENCOUNTER_TABLE = "cci.FactExplorationPopulationEncounter"; + private readonly CentralDbContext _centralDbContext; + private readonly IPostgresReplicationService _postgresReplicationService; + private readonly ILogger _logger; + + public ExplorationPopulationReplicationJob( + CentralDbContext centralDbContext, + IPostgresReplicationService postgresReplicationService, + ILogger logger) + { + _centralDbContext = centralDbContext; + _postgresReplicationService = postgresReplicationService; + _logger = logger; + } + + [DeleteOnSuccess] + [JobDisplayName("Replicate ExplorationPopulations to All Clients")] + [Semaphore(nameof(ReplicateAllClientExplorationPopulations))] + public async Task ReplicateAllClientExplorationPopulations(CancellationToken cancellationToken) + { + try + { + var expPopulationsByClient = await _centralDbContext.ExplorationPopulations.Where(exp => exp.StrataId != 0).GroupBy(exp => exp.StrataId).ToListAsync(); + await _postgresReplicationService.Replicate(expPopulationsByClient, EXPLORATION_POPULATION_TABLE, cancellationToken); + + var expPopEncountersByClient = await _centralDbContext.ExplorationPopulationEncounters.Where(exp => exp.StrataId != 0).GroupBy(exp => exp.StrataId).ToListAsync(); + await _postgresReplicationService.Replicate + (expPopEncountersByClient, EXPLORATION_POPULATION_ENCOUNTER_TABLE, cancellationToken); + + _logger.LogServiceInformation("Replication of Exploration Population tables completed successfully."); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "Replication of Exploration Population tables failed."); + throw; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationService.cs b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationService.cs new file mode 100644 index 0000000..3e0d35e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationPopulationService.cs @@ -0,0 +1,686 @@ +using Hangfire; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Mappers; +using Strata.ContinuousImprovement.Biz.Replication; +using Strata.ContinuousImprovement.Biz.Replication.Models; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.ContinuousImprovement.Biz.UtilizationVariation; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.DecisionSupport.Models.Jazz; +using Strata.FeatureFlags.Client; +using Strata.Hangfire.Jazz.Client; +using Strata.Hangfire.Jazz.Client.Models; +using Strata.Id.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + + +namespace Strata.ContinuousImprovement.Biz.Population +{ + public class ExplorationPopulationService : IExplorationPopulationService + { + private readonly IAsyncDbContextFactory _jazzContextFactory; + private readonly CentralDbContext _centralDbContext; + private readonly ClaimsPrincipal _user; + private readonly IJazzHangfireServiceClient _hangfireServiceClient; + private readonly IBackgroundJobClient _backgroundJobClient; + private readonly IConfigurationService _configurationService; + private readonly ILogger _logger; + private readonly IPostgresReplicationService _postgresReplicationService; + private readonly IIdServiceClient _idServiceClient; + private readonly IFeatureFlagServiceClient _featureFlagServiceClient; + + public ExplorationPopulationService(IAsyncDbContextFactory jazzContextFactory, CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, IJazzHangfireServiceClient hangfireServiceClient, IBackgroundJobClient backgroundJobClient, + IConfigurationService configurationService, ILogger logger, IPostgresReplicationService postgresReplicationService, + IIdServiceClient idServiceClient, IFeatureFlagServiceClient featureFlagServiceClient) + { + _jazzContextFactory = jazzContextFactory; + _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + _centralDbContext = centralDbContext; + _hangfireServiceClient = hangfireServiceClient; + _backgroundJobClient = backgroundJobClient; + _configurationService = configurationService; + _logger = logger; + _postgresReplicationService = postgresReplicationService; + _idServiceClient = idServiceClient; + _featureFlagServiceClient = featureFlagServiceClient; + } + + public async Task IsNameUnique(string name, CancellationToken cancellationToken) + { + var result = await _centralDbContext.ExplorationPopulations.Where(item => item.StrataId == _user.GetStrataId()).Select(item => item.Name).ContainsAsync(name, cancellationToken); + return !result; + } + + public async Task AddPopulationAsync(ExplorationPopulationDto populationDto, CancellationToken cancellationToken) + { + await using var dbContext = await _jazzContextFactory.CreateDbContextAsync(cancellationToken); + var isUnique = await IsNameUnique(populationDto.Name, cancellationToken); + + if (!isUnique) + { + var item = await _centralDbContext.ExplorationPopulations.FirstOrDefaultAsync(item => item.Name == populationDto.Name && item.StrataId == _user.GetStrataId(), cancellationToken); + populationDto.ExplorationPopulationId = item.ExplorationPopulationId; + + return await UpdatePopulationAsync(populationDto, cancellationToken); + } + else + { + var strategy = _centralDbContext.Database.CreateExecutionStrategy(); + + var isAdded = await strategy.ExecuteAsync(async () => + { + using var transaction = await dbContext.Database.BeginTransactionAsync(); + using var centralTransaction = await _centralDbContext.Database.BeginTransactionAsync(); + try + { + var population = (ExplorationPopulation)populationDto; + population.StrataId = _user.GetStrataId(); + _centralDbContext.ExplorationPopulations.Add(population); + + var isPopulationAdded = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + + var databaseGuid = _user.GetDatabaseGuid(); + dbContext.DimCaseTypes.Add(new DimCaseType + { + Name = population.Name, + CaseTypeFamilyId = population.CaseTypeFamilyId, + CaseTypeconfigJSON = "{}", + IsAllEntity = true, + CaseTypeCategory = (int)CaseTypeCategories.ExplorationPopulation, + ExplorationPopulationId = population.ExplorationPopulationId + }); + + await dbContext.SaveChangesAsync(cancellationToken); + + var mappings = populationDto.MappingEncounterIds.Select(encID => + new ExplorationPopulationEncounter(population.ExplorationPopulationId, encID) { StrataId = _user.GetStrataId() }); + _centralDbContext.ExplorationPopulationEncounters.AddRange(mappings); + + var excludedEncounters = populationDto.ManuallyExcludedEncounterIds.Select(encID => + new ExplorationInclusionExclusion(population.ExplorationPopulationId, encID, true) { StrataId = _user.GetStrataId() }); + _centralDbContext.ExplorationInclusionExclusions.AddRange(excludedEncounters); + + var includedEncounters = populationDto.ManuallyIncludedEncounterIds.Select(encID => + new ExplorationInclusionExclusion(population.ExplorationPopulationId, encID, false) { StrataId = _user.GetStrataId() }); + _centralDbContext.ExplorationInclusionExclusions.AddRange(includedEncounters); + + await _centralDbContext.SaveChangesAsync(cancellationToken); + + await transaction.CommitAsync(); + await centralTransaction.CommitAsync(); + + await ReplicateExplorationPopulations(cancellationToken); + + return isPopulationAdded; + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + await transaction.RollbackAsync(); + await centralTransaction.RollbackAsync(); + throw; + } + }); + + return isAdded; + } + } + + public async Task UpdatePopulationAsync(ExplorationPopulationDto populationDto, CancellationToken cancellationToken) + { + var databaseGuid = _user?.GetDatabaseGuid() ?? Guid.Empty; + var strataId = _user.GetStrataId(); + var strategicOpportunityWizardFeatureFlag = await _featureFlagServiceClient + .IsEnabledAsync(FeatureFlagLookup.Values[FeatureFlag.CiExplorationPopulationStrategicOpportunityWizard], databaseGuid, cancellationToken: cancellationToken); + var strategicOpportunities = new List(); + + if (strategicOpportunityWizardFeatureFlag) + { + strategicOpportunities = await _centralDbContext.StrategicOpportunityChargeCodes + .Where(x => x.ExplorationPopulationId == populationDto.ExplorationPopulationId && x.StrataId == strataId) + .ToListAsync(cancellationToken); + + if (strategicOpportunities.Any(x => x.Status >= (byte)StrategicOpportunityStatus.NewInitiative)) + return false; + } + + var strategy = _centralDbContext.Database.CreateExecutionStrategy(); + + var isUpdated = await strategy.ExecuteAsync(async () => + { + using var transaction = await _centralDbContext.Database.BeginTransactionAsync(); + try + { + var population = (ExplorationPopulation)populationDto; + population.StrataId = strataId; + _centralDbContext.ExplorationPopulations.Update(population); + + var savedMappings = await _centralDbContext.ExplorationPopulationEncounters.Where(item => item.ExplorationPopulationId == populationDto.ExplorationPopulationId + && item.StrataId == strataId).ToListAsync(cancellationToken); + var savedMappingIds = savedMappings.Select(item => item.EncounterId); + var deleteMappingIds = savedMappingIds.Except(populationDto.MappingEncounterIds).ToList(); + var newMappings = populationDto.MappingEncounterIds.Except(savedMappingIds).Select(encID => + new ExplorationPopulationEncounter(population.ExplorationPopulationId, encID) { StrataId = strataId }); + var deleteMappings = savedMappings.Where(item => deleteMappingIds.Contains(item.EncounterId)); + + _centralDbContext.ExplorationPopulationEncounters.RemoveRange(deleteMappings); + _centralDbContext.ExplorationPopulationEncounters.AddRange(newMappings); + + var explorationInclusionExclusions = _centralDbContext.ExplorationInclusionExclusions.Where(item => item.ExplorationPopulationId == populationDto.ExplorationPopulationId + && item.StrataId == strataId); + var explorationInclusionExclusionIds = explorationInclusionExclusions.Select(item => item.EncounterId); + + var deleteIds = await explorationInclusionExclusionIds.Except(populationDto.ManuallyExcludedEncounterIds).Except(populationDto.ManuallyIncludedEncounterIds).ToListAsync(); + var deleteItems = explorationInclusionExclusions.Where(item => deleteIds.Contains(item.EncounterId)); + var newExc = populationDto.ManuallyExcludedEncounterIds.Except(explorationInclusionExclusionIds); + var newInc = populationDto.ManuallyIncludedEncounterIds.Except(explorationInclusionExclusionIds); + + _centralDbContext.ExplorationInclusionExclusions.RemoveRange(deleteItems); + _centralDbContext.ExplorationInclusionExclusions.AddRange(newExc.Select(encID => + new ExplorationInclusionExclusion(population.ExplorationPopulationId, encID, true) { StrataId = strataId })); + _centralDbContext.ExplorationInclusionExclusions.AddRange(newInc.Select(encID => + new ExplorationInclusionExclusion(population.ExplorationPopulationId, encID, false) { StrataId = strataId })); + + var isUpdated = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + await transaction.CommitAsync(); + + if (isUpdated) await ReplicateExplorationPopulations(cancellationToken); + + return isUpdated; + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + await transaction.RollbackAsync(); + throw; + } + }); + + if (strategicOpportunityWizardFeatureFlag && strategicOpportunities.Any()) + { + foreach (var opportunity in strategicOpportunities) + { + _backgroundJobClient.Enqueue(job => + job.RefreshAssociatedOpportunity(strataId, opportunity.OpportunityId, CancellationToken.None)); + } + } + + return isUpdated; + } + + public async Task UpdatePopulationMappingsAsync(long explorationPopulationId, IEnumerable newIncludedEncounterIds, IEnumerable newExcludedEncounterIds, CancellationToken cancellationToken) + { + var strategy = _centralDbContext.Database.CreateExecutionStrategy(); + + var isUpdated = await strategy.ExecuteAsync(async () => + { + using var transaction = await _centralDbContext.Database.BeginTransactionAsync(); + try + { + // update ExplorationPopulationMappings + var savedMappings = await _centralDbContext.ExplorationPopulationEncounters.Where(x => x.ExplorationPopulationId == explorationPopulationId + && x.StrataId == _user.GetStrataId()).ToListAsync(cancellationToken); + var savedMappingEncounterIds = savedMappings.Select(x => x.EncounterId); + + var toDeleteMappingIds = savedMappingEncounterIds.Except(newIncludedEncounterIds); + var toDeleteMappings = savedMappings.Where(x => toDeleteMappingIds.Contains(x.EncounterId)); + _centralDbContext.ExplorationPopulationEncounters.RemoveRange(toDeleteMappings); + + var newMappings = newIncludedEncounterIds.Except(savedMappingEncounterIds).Select(encID => + new ExplorationPopulationEncounter(explorationPopulationId, encID) { StrataId = _user.GetStrataId() }); + _centralDbContext.ExplorationPopulationEncounters.AddRange(newMappings); + + // update ExplorationInclusionExclusions + var toDeleteInclusionExclusions = await _centralDbContext.ExplorationInclusionExclusions.Where(x => x.ExplorationPopulationId == explorationPopulationId && x.StrataId == _user.GetStrataId() + && ((!newIncludedEncounterIds.Contains(x.EncounterId) && !x.IsExcluded) || (!newExcludedEncounterIds.Contains(x.EncounterId) && x.IsExcluded))).ToListAsync(cancellationToken); + _centralDbContext.ExplorationInclusionExclusions.RemoveRange(toDeleteInclusionExclusions); + + var isUpdated = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + transaction.Commit(); + + return isUpdated; + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + transaction.Rollback(); + throw; + } + }); + + return isUpdated; + } + + public async Task UpdatePopulationName(long explorationPopulationId, string populationName, CancellationToken cancellationToken) + { + var population = await _centralDbContext.ExplorationPopulations + .FirstOrDefaultAsync(item => item.ExplorationPopulationId == explorationPopulationId && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (population == null) return false; + + population.Name = populationName; + var isUpdated = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + return isUpdated; + } + + public async Task> GetPopulationsAsync(CancellationToken cancellationToken) + { + try + { + var databaseGuid = _user?.GetDatabaseGuid() ?? Guid.Empty; + var opportunities = await GetPopulationOpportunitiesAsync(null, cancellationToken); + + var items = await _centralDbContext.ExplorationPopulations.Where(x => x.StrataId == _user.GetStrataId()).Select(item => new ExplorationPopulationDataDto() + { + ExplorationPopulationId = item.ExplorationPopulationId, + Name = item.Name, + TotalEncounters = item.TotalEncounters, + TotalVariation = item.TotalVariation, + StartDischargeDate = item.MapToExplorationFilter().DischargeDateStart, + EndDischargeDate = item.MapToExplorationFilter().DischargeDateEnd, + Opportunities = opportunities[item.ExplorationPopulationId] + }).OrderBy(item => item.Name).ToListAsync(cancellationToken); + + if (!await _featureFlagServiceClient.IsEnabledAsync(FeatureFlagLookup.Values[FeatureFlag.CiExplorationPopulationStrategicOpportunityWizard], databaseGuid, cancellationToken: cancellationToken)) + return items; + + var explorationPopulationIds = items.Select(x => x.ExplorationPopulationId).ToList(); + + var strategicCounts = await _centralDbContext.StrategicOpportunityChargeCodes + .Where(x => explorationPopulationIds.Contains(x.ExplorationPopulationId) && x.StrataId == _user.GetStrataId()) + .GroupBy(x => x.ExplorationPopulationId) + .Select(x => new + { + ExplorationPopulationId = x.Key, + AssociatedOpportunitiesCount = x.Count(x => x.Status < (byte)StrategicOpportunityStatus.NewInitiative), + AssociatedInitiativesCount = x.Count(x => x.Status >= (byte)StrategicOpportunityStatus.NewInitiative) + }) + .ToListAsync(cancellationToken); + + var countLookup = strategicCounts.ToDictionary(x => x.ExplorationPopulationId); + + foreach (var item in items) + { + if (countLookup.TryGetValue(item.ExplorationPopulationId, out var counts)) + { + item.AssociatedOpportunitiesCount = counts.AssociatedOpportunitiesCount; + item.AssociatedInitiativesCount = counts.AssociatedInitiativesCount; + } + } + + return items; + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "Error getting exploration populations"); + throw; + } + } + + public async Task> GetPopulationOpportunitiesAsync(IEnumerable explorationPopulationIds, CancellationToken cancellationToken) + { + if (_user == null) + { + throw new InvalidOperationException("User context is required to get population opportunities."); + } + var databaseGuid = _user.GetDatabaseGuid(); + var strataId = _user.GetStrataId(); + List opportunities = []; + + if (await _featureFlagServiceClient.IsEnabledAsync(FeatureFlagLookup.Values[FeatureFlag.CiExplorationPopulationStrategicOpportunityWizard], databaseGuid, cancellationToken: cancellationToken)) + { + var strategicOpportunitiesDtos = await ( + from socc in _centralDbContext.StrategicOpportunityChargeCodes + join so in _centralDbContext.StrategicOpportunities + on socc.OpportunityId equals so.OpportunityId + where socc.ExplorationPopulationId != 0 && socc.StrataId == strataId + select new ExplorationPopulationOpportunityDto + { + OpportunityId = so.OpportunityId, + ExplorationPopulationId = socc.ExplorationPopulationId, + BaselineStartDate = so.BaselineStartDate.ToDateTime(TimeOnly.MinValue), + BaselineEndDate = so.BaselineEndDate.ToDateTime(TimeOnly.MinValue) + } + ).ToListAsync(cancellationToken); + + opportunities.AddRange(strategicOpportunitiesDtos); + } + else + { + await using var dbContext = await _jazzContextFactory.CreateDbContextAsync(cancellationToken); + + var populationOpportunitiesQuery = new PopulationOpportunitiesQuery(explorationPopulationIds); + var query = FormattableStringFactory.Create(populationOpportunitiesQuery.GetQuery()); + opportunities = await dbContext.Database.SqlQuery(query).ToListAsync(cancellationToken); + } + + var configurations = await _configurationService.GetConfigurationsAsync(cancellationToken); + var opportunityExplorationPopulationIds = opportunities.Select(item => item.ExplorationPopulationId); + var explorationPopulations = await _centralDbContext.ExplorationPopulations.Where(item => opportunityExplorationPopulationIds.Contains(item.ExplorationPopulationId) + && item.StrataId == strataId).ToListAsync(cancellationToken); + + if (opportunities.Count > 0) + { + opportunities.ForEach(opp => + { + var configuration = configurations.FirstOrDefault(item => item.ConfigurationGuid == opp.ConfigurationGuid); + var explorationPopulation = explorationPopulations.FirstOrDefault(item => item.ExplorationPopulationId == opp.ExplorationPopulationId); + + if (explorationPopulation != null && configuration != null) + { + var explorationPopulationDischargeDates = explorationPopulation.GetDischargeDates(); + + opp.BaselineStartDate = explorationPopulationDischargeDates.Item1; + opp.BaselineEndDate = explorationPopulationDischargeDates.Item2; + + if (opp.InitiativeGuid != Guid.Empty && configuration != null) + { + opp.TrackingStartDate = GetTrackingStartDate(explorationPopulationDischargeDates.Item2, configuration); + opp.TrackingEndDate = configuration.GetTrackingEndDate(); + } + } + }); + } + return opportunities.ToLookup(x => x.ExplorationPopulationId); + } + + public DateTime GetTrackingStartDate(DateTime dischargeEndDate, Configuration.Configuration configuration) + { + return dischargeEndDate.AddDays(1) < configuration.GetTrackingStartDate(Generics.OpportunityTypes.Exploration) + ? configuration.GetTrackingStartDate(Generics.OpportunityTypes.Exploration) + : dischargeEndDate.AddDays(1); + } + + public async Task<(DateTime, DateTime)> GetDischargeDates(long explorationPopulationId, CancellationToken cancellationToken) + { + var expPopulation = await _centralDbContext.ExplorationPopulations.FirstAsync(item => item.ExplorationPopulationId == explorationPopulationId && item.StrataId == _user.GetStrataId(), cancellationToken); + return expPopulation.GetDischargeDates(); + } + + public async Task> GetPopulationCountOpportunitiesAsync(IEnumerable explorationPopulationIds, CancellationToken cancellationToken) + { + await using var dbContext = await _jazzContextFactory.CreateDbContextAsync(cancellationToken); + + var populationOpportunitiesQuery = new PopulationOpportunitiesQuery(explorationPopulationIds); + var query = FormattableStringFactory.Create(populationOpportunitiesQuery.GetCountQuery()); + + var opportunities = await dbContext.Database.SqlQuery>(query).ToListAsync(cancellationToken); + + return opportunities.ToDictionary(x => x.Key, x => x.Value); + } + + public async Task GetPopulationDataAsync(long populationId, CancellationToken cancellationToken) + { + try + { + var databaseGuid = _user?.GetDatabaseGuid() ?? Guid.Empty; + var explorationInclusionExclusions = await _centralDbContext.ExplorationInclusionExclusions.Where(item => item.ExplorationPopulationId == populationId + && item.StrataId == _user.GetStrataId()).ToListAsync(cancellationToken); + var expPopulation = await _centralDbContext.ExplorationPopulations.FirstAsync(item => item.ExplorationPopulationId == populationId && item.StrataId == _user.GetStrataId(), cancellationToken); + + ExplorationPopulationDto result = expPopulation.MapToExplorationPopulationDto(); + result.ManuallyExcludedEncounterIds = explorationInclusionExclusions.Where(x => x.IsExcluded).Select(x => x.EncounterId).ToList(); + result.ManuallyIncludedEncounterIds = explorationInclusionExclusions.Where(x => !x.IsExcluded).Select(x => x.EncounterId).ToList(); + + if (await _featureFlagServiceClient.IsEnabledAsync(FeatureFlagLookup.Values[FeatureFlag.CiExplorationPopulationStrategicOpportunityWizard], databaseGuid, cancellationToken: cancellationToken)) + { + var opportunities = _centralDbContext.StrategicOpportunityChargeCodes.Where(x => x.ExplorationPopulationId == populationId && x.StrataId == _user.GetStrataId()); + + result.AssociatedOpportunitiesCount = await opportunities.CountAsync(x => x.Status < (byte)StrategicOpportunityStatus.NewInitiative, cancellationToken); + result.AssociatedInitiativesCount = await opportunities.CountAsync(x => x.Status >= (byte)StrategicOpportunityStatus.NewInitiative, cancellationToken); + } + + var counts = await GetPopulationCountOpportunitiesAsync([populationId], cancellationToken); + if (counts.TryGetValue(populationId, out int opportunitiesCount)) + { + result.OpportunitiesCount = opportunitiesCount; + } + + return result; + } + catch (Exception ex) + { + _logger.LogContextError(ex, _centralDbContext, nameof(GetPopulationDataAsync)); + throw; + } + } + + public async Task GetPopulationDetailsAsync(long populationId, CancellationToken cancellationToken) + { + var databaseGuid = _user?.GetDatabaseGuid() ?? Guid.Empty; + var strategicsItems = new List(); + + var explorationPopulation = await _centralDbContext.ExplorationPopulations.FirstOrDefaultAsync(item => item.ExplorationPopulationId == populationId + && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (explorationPopulation == null) return null; + + var hasManuallyChangeEncounters = await _centralDbContext.ExplorationInclusionExclusions.AnyAsync(item => item.ExplorationPopulationId == populationId + && item.StrataId == _user.GetStrataId(), cancellationToken); + + var featureFlag = await _featureFlagServiceClient.IsEnabledAsync(FeatureFlagLookup.Values[FeatureFlag.CiExplorationPopulationStrategicOpportunityWizard], databaseGuid, cancellationToken: cancellationToken); + + if (featureFlag) + strategicsItems = await _centralDbContext.StrategicOpportunityChargeCodes.Where(x => x.ExplorationPopulationId == populationId && x.StrataId == _user.GetStrataId()).ToListAsync(cancellationToken); + + var explorationPopulationDetails = new ExplorationPopulationDetailsDto + { + ExplorationPopulationId = explorationPopulation.ExplorationPopulationId, + Name = explorationPopulation.Name, + CostDriver = explorationPopulation.CostDriver, + ExclusionCriteria = new ExclusionCriteria + { + CostExclusionType = (ExclusionCriteriaType)explorationPopulation.CostExclusionType, + CostExclusionValue = (double)explorationPopulation.CostExclusionValue, + LosExclusionType = (ExclusionCriteriaType)explorationPopulation.LosExclusionType, + LosExclusionValue = (double)explorationPopulation.LosExclusionValue + }, + Filters = explorationPopulation.FilterChipItems, + HasManuallyChangeEncounters = hasManuallyChangeEncounters, + AssociatedOpportunities = featureFlag ? [.. strategicsItems.Where(x => x.Status < (byte)StrategicOpportunityStatus.NewInitiative).Select(x => new ExplorationPopulationStrategicOpportunityDto(x.OpportunityId, x.Name))] : [], + AssociatedInitiatives = featureFlag ? [.. strategicsItems.Where(x => x.Status >= (byte)StrategicOpportunityStatus.NewInitiative).Select(x => new ExplorationPopulationStrategicOpportunityDto(x.OpportunityId, x.Name))] : [] + }; + + return explorationPopulationDetails; + } + + public async Task GetPopulationInfoAsync(long populationId, CancellationToken cancellationToken) + { + var population = await _centralDbContext.ExplorationPopulations.FirstOrDefaultAsync(item => item.ExplorationPopulationId == populationId + && item.StrataId == _user.GetStrataId(), cancellationToken); + + return population?.MapToExplorationPopulationInfo(); + } + + public async Task DeletePopulation(long populationId, CancellationToken cancellationToken) + { + await using var dbContext = await _jazzContextFactory.CreateDbContextAsync(cancellationToken); + + var explorationPopulation = await _centralDbContext.ExplorationPopulations.SingleOrDefaultAsync(item => item.ExplorationPopulationId == populationId + && item.StrataId == _user.GetStrataId(), cancellationToken); + + var strategy = _centralDbContext.Database.CreateExecutionStrategy(); + + var isDeleted = await strategy.ExecuteAsync(async () => + { + using var transaction = await dbContext.Database.BeginTransactionAsync(); + using var centralTransaction = await _centralDbContext.Database.BeginTransactionAsync(); + try + { + var mappings = _centralDbContext.ExplorationPopulationEncounters.Where(item => item.ExplorationPopulationId == populationId && item.StrataId == _user.GetStrataId()); + _centralDbContext.ExplorationPopulationEncounters.RemoveRange(mappings); + + var manuallyChangeEncounters = _centralDbContext.ExplorationInclusionExclusions.Where(item => item.ExplorationPopulationId == populationId && item.StrataId == _user.GetStrataId()); + _centralDbContext.ExplorationInclusionExclusions.RemoveRange(manuallyChangeEncounters); + + await _centralDbContext.SaveChangesAsync(cancellationToken); + + var dimCaseType = dbContext.DimCaseTypes.Where(item => item.ExplorationPopulationId == populationId); + dbContext.DimCaseTypes.RemoveRange(dimCaseType); + + await dbContext.SaveChangesAsync(cancellationToken); + + _centralDbContext.ExplorationPopulations.Remove(explorationPopulation); + + var isDeleted = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + await transaction.CommitAsync(); + await centralTransaction.CommitAsync(); + + if (isDeleted) await ReplicateExplorationPopulations(cancellationToken); + + return isDeleted; + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + await transaction.RollbackAsync(); + await centralTransaction.RollbackAsync(); + throw; + } + }); + + return isDeleted; + } + + public async Task DuplicatePopulation(long explorationPopulationId, string populationName, CancellationToken cancellationToken) + { + var strataId = _user.GetStrataId(); + + var population = await _centralDbContext.ExplorationPopulations + .FirstOrDefaultAsync(item => item.ExplorationPopulationId == explorationPopulationId && item.StrataId == strataId, cancellationToken); + + if (population == null) return null; + + var explorationPopulation = await GetPopulationDataAsync(explorationPopulationId, cancellationToken); + explorationPopulation.ExplorationPopulationId = 0; + explorationPopulation.Name = populationName; + + var mappings = await _centralDbContext.ExplorationPopulationEncounters.Where(item => item.ExplorationPopulationId == explorationPopulationId + && item.StrataId == strataId).ToListAsync(cancellationToken); + + explorationPopulation.MappingEncounterIds = [.. mappings.Select(item => item.EncounterId)]; + explorationPopulation.ManuallyExcludedEncounterIds ??= []; + explorationPopulation.ManuallyIncludedEncounterIds ??= []; + + await AddPopulationAsync(explorationPopulation, cancellationToken); + + if (explorationPopulation.ExplorationPopulationId == 0) return null; + + var expPopulationFilter = explorationPopulation.MapToExplorationFilter(); + + var duplicatedPopulation = new ExplorationPopulationDataDto() + { + ExplorationPopulationId = explorationPopulation.ExplorationPopulationId, + Name = explorationPopulation.Name, + TotalEncounters = explorationPopulation.TotalEncounters, + TotalVariation = explorationPopulation.TotalVariation, + AssociatedInitiativesCount = 0, + AssociatedOpportunitiesCount = 0, + StartDischargeDate = expPopulationFilter.DischargeDateStart, + EndDischargeDate = expPopulationFilter.DischargeDateEnd, + Opportunities = [] + }; + + return duplicatedPopulation; + } + + public async Task ContainsPopulation(long populationId, CancellationToken cancellationToken) + { + var explorationPopulation = await _centralDbContext.ExplorationPopulations.AnyAsync(item => item.ExplorationPopulationId == populationId + && item.StrataId == _user.GetStrataId(), cancellationToken); + + return explorationPopulation; + } + + public async Task CreateExplorationPopulationOpportunity(Guid configurationGuid, long populationId, bool waitForJob, CancellationToken cancellationToken) + { + try + { + await using var dbContext = await _jazzContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext != null) + { + var caseType = await dbContext.DimCaseTypes.SingleOrDefaultAsync(x => x.ExplorationPopulationId == populationId); + if (caseType != null) + { + var caseTypeGuids = new List() { caseType.MemberGUID }; + var jobId = await RunExplorationVariationSavingsJob(configurationGuid, caseTypeGuids, cancellationToken); + + if (waitForJob) + { + var result = await _hangfireServiceClient.WaitForJob(jobId, cancellationToken); + return result; + } + else + { + return true; + } + + } + } + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "Error adding exploration population opportunity"); + throw; + } + return false; + } + + public async Task ReplicateExplorationPopulations(CancellationToken cancellationToken) + { + const string EXPLORATION_POPULATION_TABLE = "cci.DimExplorationPopulation"; + const string EXPLORATION_POPULATION_ENCOUNTER_TABLE = "cci.FactExplorationPopulationEncounter"; + + try + { + var strataId = await _idServiceClient.GetStrataId(_user.GetDatabaseGuid(), cancellationToken); + + var expPopulationsByClient = await _centralDbContext.ExplorationPopulations.Where(exp => exp.StrataId == strataId).GroupBy(exp => exp.StrataId).ToListAsync(); + await _postgresReplicationService.Replicate(expPopulationsByClient, EXPLORATION_POPULATION_TABLE, cancellationToken); + + var expPopEncountersByClient = await _centralDbContext.ExplorationPopulationEncounters.Where(exp => exp.StrataId == strataId).GroupBy(exp => exp.StrataId).ToListAsync(); + await _postgresReplicationService.Replicate + (expPopEncountersByClient, EXPLORATION_POPULATION_ENCOUNTER_TABLE, cancellationToken); + + _logger.LogServiceInformation("Replication of Exploration Population tables completed successfully."); + return true; + } + catch (Exception ex) + { + _logger.LogServiceError(ex, "Replication of Exploration Population tables failed."); + return false; + } + } + + private async Task RunExplorationVariationSavingsJob(Guid configurationGuid, IEnumerable caseTypeGuids, CancellationToken cancellationToken) + { + var runSavingsJob = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.Exploration.RunExplorationVariationSavingsTask, Strata.CS.Jazz.Biz.CCI", + ContextData = JsonConvert.SerializeObject(new { configurationGuid, caseTypeGuids }) + }; + + var response = await _hangfireServiceClient.EnqueueJobAsync(runSavingsJob, cancellationToken); + return response.JobId; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Population/ExplorationVariationOpportunityDataModel.cs b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationVariationOpportunityDataModel.cs new file mode 100644 index 0000000..c745f83 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Population/ExplorationVariationOpportunityDataModel.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Population +{ + [Table("ExplorationVariationOpportunity", Schema = "cci")] + public class ExplorationVariationOpportunityDataModel + { + [Key] + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public short YearConsidered { get; set; } + public string CostDriver { get; set; } + public byte SavingsLevel { get; set; } + public double Savings { get; set; } + public double SavingsMedian { get; set; } + public double SavingsTarget { get; set; } + public int TotalCases { get; set; } + public double TotalCost { get; set; } + public double TotalVariableCost { get; set; } + public double CostMedian { get; set; } + public double CostMedicare { get; set; } + public double VariableCostMedian { get; set; } + public double VariableCostMedicare { get; set; } + public string Note { get; set; } + public DateTime DateModified { get; set; } + public DateTime DateCreated { get; set; } + public Guid ConfigurationGuid { get; set; } + public bool IsHidden { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Population/IExplorationPopulationService.cs b/src/Strata.ContinuousImprovement.Biz/Population/IExplorationPopulationService.cs new file mode 100644 index 0000000..44ba762 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Population/IExplorationPopulationService.cs @@ -0,0 +1,31 @@ +using Strata.ContinuousImprovement.Biz.Dtos; +using Strata.DecisionSupport.Models.Jazz; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Population +{ + public interface IExplorationPopulationService + { + Task IsNameUnique(string name, CancellationToken cancellationToken); + Task AddPopulationAsync(ExplorationPopulationDto populationDto, CancellationToken cancellationToken); + Task UpdatePopulationAsync(ExplorationPopulationDto populationDto, CancellationToken cancellationToken); + Task UpdatePopulationMappingsAsync(long explorationPopulationId, IEnumerable newIncludedEncounterIds, IEnumerable newExcludedEncounterIds, CancellationToken cancellationToken); + Task UpdatePopulationName(long explorationPopulationId, string populationName, CancellationToken cancellationToken); + Task> GetPopulationsAsync(CancellationToken cancellationToken); + Task> GetPopulationOpportunitiesAsync(IEnumerable explorationPopulationIds, CancellationToken cancellationToken); + Task> GetPopulationCountOpportunitiesAsync(IEnumerable explorationPopulationIds, CancellationToken cancellationToken); + Task GetPopulationDataAsync(long populationId, CancellationToken cancellationToken); + Task GetPopulationDetailsAsync(long populationId, CancellationToken cancellationToken); + Task GetPopulationInfoAsync(long populationId, CancellationToken cancellationToken); + Task DeletePopulation(long populationId, CancellationToken cancellationToken); + Task DuplicatePopulation(long explorationPopulationId, string populationName, CancellationToken cancellationToken); + Task ContainsPopulation(long populationId, CancellationToken cancellationToken); + Task CreateExplorationPopulationOpportunity(Guid configurationGuid, long populationId, bool waitForJob, CancellationToken cancellationToken); + Task ReplicateExplorationPopulations(CancellationToken cancellationToken); + Task<(DateTime, DateTime)> GetDischargeDates(long explorationPopulationId, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Population/PopulationOpportunitiesQuery.cs b/src/Strata.ContinuousImprovement.Biz/Population/PopulationOpportunitiesQuery.cs new file mode 100644 index 0000000..302f804 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Population/PopulationOpportunitiesQuery.cs @@ -0,0 +1,57 @@ +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.GeneralLedger.Opportunities; +using Strata.ContinuousImprovement.Biz.Shared; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Population +{ + public class PopulationOpportunitiesQuery + { + private readonly IEnumerable _explorationPopulationIds; + public PopulationOpportunitiesQuery(IEnumerable explorationPopulationIds) + { + _explorationPopulationIds = explorationPopulationIds; + } + public string GetQuery() + { + var sqlWhere = _explorationPopulationIds != null && _explorationPopulationIds.Any() ? $"WHERE dct.ExplorationPopulationId IN ({string.Join(", ", _explorationPopulationIds)})" : string.Empty; + return $@" +SELECT + evo.OpportunityGUID, + evo.OpportunityID, + evo.InitiativeGUID, + evo.CostDriver, + evo.ConfigurationGUID, + dct.ExplorationPopulationId, + NULL AS BaselineStartDate, + NULL AS BaselineEndDate, + NULL AS TrackingStartDate, + NULL AS TrackingEndDate +FROM cci.ExplorationVariationOpportunity evo +INNER JOIN dss.DimCaseType dct ON dct.CaseTypeId = evo.CaseTypeId +INNER JOIN cci.Configuration conf ON conf.ConfigurationGUID = evo.ConfigurationGUID +{sqlWhere} +ORDER BY conf.FiscalYearID DESC + "; + } + + public string GetCountQuery() + { + var sqlWhere = _explorationPopulationIds != null && _explorationPopulationIds.Any() ? $"WHERE dct.ExplorationPopulationId IN ({string.Join(", ", _explorationPopulationIds)})" : string.Empty; + return $@" +SELECT + dct.ExplorationPopulationId AS [Key], + COUNT(1) AS [Value] +FROM cci.ExplorationVariationOpportunity evo +INNER JOIN dss.DimCaseType dct ON dct.CaseTypeId = evo.CaseTypeId +{sqlWhere} +GROUP BY dct.ExplorationPopulationId + "; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/Filters/QVIBreakdownFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/Filters/QVIBreakdownFilterOptions.cs new file mode 100644 index 0000000..2a420a6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/Filters/QVIBreakdownFilterOptions.cs @@ -0,0 +1,11 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns.Filters +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIBreakdownFilterOptions + { + public IEnumerable Breakdowns { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/Filters/QVIBreakdownFilters.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/Filters/QVIBreakdownFilters.cs new file mode 100644 index 0000000..fd45c8a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/Filters/QVIBreakdownFilters.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Filter.Expressions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns.Filters +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIBreakdownFilters : IFilters + { + public bool ExportFiltered { get; set; } + public Guid ConfigurationGuid { get; set; } + public int IdentifiedSavings { get; set; } + public IEnumerable BreakdownIds { get; set; } + public int QualityVariationCases { get; set; } + + public QVIBreakdownFilters() : base() + { + ExportFiltered = true; + ConfigurationGuid = Guid.Empty; + IdentifiedSavings = 0; + BreakdownIds = new List(); + QualityVariationCases = 0; + } + + public IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = new List>>(); + + filters.AddConfigurationGuidFilter(ConfigurationGuid); + filters.AddIdentifiedSavingsTotalFilter(IdentifiedSavings); + + if (BreakdownIds.Any()) + filters.Add(m => BreakdownIds.Contains(m.BreakdownId.ToString())); + + if (QualityVariationCases > 0) + filters.Add(m => m.QVICasesTotal >= QualityVariationCases); + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/IQVIBreakdownService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/IQVIBreakdownService.cs new file mode 100644 index 0000000..7c0d9f5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/IQVIBreakdownService.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns.Filters; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public interface IQVIBreakdownService : IJazzDbService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/QVIBreakdown.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/QVIBreakdown.cs new file mode 100644 index 0000000..ee7bf8e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/QVIBreakdown.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns +{ + [Keyless] + [Table("viewQualityVariationItemBreakdowns", Schema = "cci")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIBreakdown : IWithConfigurationGuid, IWithIdentifiedSavingsTotal + { + public int BreakdownId { get; set; } + public string BreakdownName { get; set; } + public Guid ConfigurationGuid { get; set; } + public int CasesTotal { get; set; } + public int QVICasesTotal { get; set; } + [Precision(29, 11)] + public decimal QVIRate { get; set; } + public double IdentifiedSavingsTotal { get; set; } + public int OpportunitiesCount { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/QVIBreakdownService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/QVIBreakdownService.cs new file mode 100644 index 0000000..fadfa1b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Breakdowns/QVIBreakdownService.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns.Filters; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Breakdowns +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIBreakdownService : JazzDbService, IQVIBreakdownService + { + public QVIBreakdownService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, IClaimsPrincipalAccessor claimsPrincipalAccessor) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor) + { + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return null; + + var breakdownContext = dbContext.Set(); + var configuredBreakdowns = breakdownContext.Where(ct => ct.ConfigurationGuid == configurationGuid); + + var breakdowns = configuredBreakdowns + .Select(vo => new FilterMember { Id = vo.BreakdownId.ToString(), Text = vo.BreakdownName }) + .Distinct().OrderBy(oa => oa.Text); + + return new QVIBreakdownFilterOptions() + { + Breakdowns = await breakdowns.ToListAsync(cancellationToken) + }; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/Filters/QVIEventFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/Filters/QVIEventFilterOptions.cs new file mode 100644 index 0000000..727153b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/Filters/QVIEventFilterOptions.cs @@ -0,0 +1,12 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Events.Filters +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIEventFilterOptions + { + public IEnumerable QualityVariationEvents { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/Filters/QVIEventFilters.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/Filters/QVIEventFilters.cs new file mode 100644 index 0000000..a70fdf1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/Filters/QVIEventFilters.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Filter.Expressions; +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Events.Filters +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIEventFilters:IFilters + { + public bool ExportFiltered { get; set; } + public Guid ConfigurationGuid { get; set; } + public IEnumerable QualityVariationEventIds { get; set; } + public int IdentifiedSavings { get; set; } + public int QVICases { get; set; } + + public QVIEventFilters() : base() + { + ExportFiltered = true; + ConfigurationGuid = Guid.Empty; + QualityVariationEventIds = new List(); + IdentifiedSavings = 0; + QVICases = 0; + } + + public IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = new List>>(); + + filters.AddConfigurationGuidFilter(ConfigurationGuid); + filters.AddIdentifiedSavingsTotalFilter(IdentifiedSavings); + + if (QualityVariationEventIds.Any()) + filters.Add(m => QualityVariationEventIds.Contains(m.QualityVariationEventId.ToString())); + + if (QVICases > 0) + filters.Add(m => m.QVICasesTotal >= QVICases); + + return filters; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/IQVIEventService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/IQVIEventService.cs new file mode 100644 index 0000000..a0a23ce --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/IQVIEventService.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.QualityVariation.Events.Filters; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Events +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public interface IQVIEventService : IJazzDbService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/QVIEvent.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/QVIEvent.cs new file mode 100644 index 0000000..d5432e8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/QVIEvent.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Events +{ + [Keyless] + [Table("viewQualityVariationItemEvents", Schema = "cci")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIEvent : IWithConfigurationGuid, IWithIdentifiedSavingsTotal + { + public int QualityVariationEventId { get; set; } + public string QualityVariationEventName { get; set; } + public Guid ConfigurationGuid { get; set; } + public double IdentifiedSavingsTotal { get; set; } + public int CasesTotal { get; set; } + public int QVICasesTotal { get; set; } + public int QVIPatientDaysTotal { get; set; } + public double QVICostToal { get; set; } + public double QVINetRevenueTotal { get; set; } + public int Opportunities { get; set; } + [Precision(29, 11)] + public decimal QVIRate { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/QVIEventService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/QVIEventService.cs new file mode 100644 index 0000000..35f1daf --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Events/QVIEventService.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.QualityVariation.Events.Filters; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Events +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIEventService : JazzDbService, IQVIEventService + { + + public QVIEventService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, IClaimsPrincipalAccessor claimsPrincipalAccessor) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor) + { + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return null; + + var qviEventContext = dbContext.Set(); + var configuredQVIEvents = qviEventContext.Where(ct => ct.ConfigurationGuid == configurationGuid); + + var qviEvents = configuredQVIEvents + .Select(vo => new FilterMember { Id = vo.QualityVariationEventId.ToString(), Text = vo.QualityVariationEventName }) + .Distinct().OrderBy(oa => oa.Text); + + return new QVIEventFilterOptions() + { + QualityVariationEvents = await qviEvents.ToListAsync(cancellationToken) + }; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Jobs/QVIOpportunityMetricsJob.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Jobs/QVIOpportunityMetricsJob.cs new file mode 100644 index 0000000..ee5df26 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Jobs/QVIOpportunityMetricsJob.cs @@ -0,0 +1,45 @@ +using Hangfire; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.QualityVariation.Metrics; +using Strata.Hangfire.Filters; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Jobs +{ + public class QviOpportunityMetricsJob + { + private readonly IQviMetricService _qviMetricService; + private readonly ILogger _logger; + + public QviOpportunityMetricsJob( + IQviMetricService qviMetricService, + ILogger logger) + { + _qviMetricService = qviMetricService; + _logger = logger; + } + + [DeleteOnSuccess] + [JobDisplayName("Populate QVI Metrics for Configuration {0} (Baseline: {1})")] + [CaptureClaimsPrincipal] + public async Task PopulateQviMetricsAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken) + { + try + { + await _qviMetricService.PopulateMetricsAsync(configurationGuid, isBaseline, cancellationToken); + } + catch (Exception ex) + { + _logger.LogError( + ex, + "Error populating QVI metrics for Configuration {ConfigurationGuid}, IsBaseline: {IsBaseline}", + configurationGuid, + isBaseline); + throw; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/IQVIMetricService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/IQVIMetricService.cs new file mode 100644 index 0000000..07b846f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/IQVIMetricService.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Metrics +{ + public interface IQviMetricService + { + Task PopulateMetricsAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/QVIMetricData.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/QVIMetricData.cs new file mode 100644 index 0000000..605d965 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/QVIMetricData.cs @@ -0,0 +1,144 @@ +using Strata.ContinuousImprovement.Biz.ServiceLines; +using System; +using CIConfiguration = Strata.ContinuousImprovement.Biz.Configuration.Configuration; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Metrics +{ + public class QviMetricData + { + public Guid ConfigurationGuid { get; set; } + public short YearId { get; set; } + public byte MonthId { get; set; } + public int PhysicianId { get; set; } + public int EntityId { get; set; } + public int FinancialClassId { get; set; } + public int InsurancePlanId { get; set; } + public int ServiceLineId { get; set; } + public int CaseTypeFamilyId { get; set; } + public int TotalInpatientCases { get; set; } + public int TotalCasesWithoutQvi { get; set; } + public int TotalCasesWithQvi { get; set; } + public int TotalPatientDaysWithoutQvi { get; set; } + public decimal NetRevenueWithoutQvi { get; set; } + public decimal NetRevenueWithQvi { get; set; } + public decimal TotalCostWithoutQvi { get; set; } + public decimal TotalCostWithQvi { get; set; } + public bool IsBaseline { get; set; } + + public static string BuildQuery(string entityIds, string sourceSystemIds, CIConfiguration config, ServiceLineInformation serviceLineInfo) + { + var caseTypeFamilyIdSqlColumn = config.GetCaseTypeFamilyIdSqlColumn(); + + var rollup = config.DepartmentRollupGlobalId; + string entityId; + string entityJoin; + var whereString = ""; + switch (rollup) + { + case "Location": + entityId = "pes.LocationId"; + entityJoin = ""; + whereString = $"AND pes.LocationID IN ({entityIds})"; + break; + case "": + entityId = "de.FwEntityID"; + entityJoin = $@" + inner join clientdss.DimEntity de on pes.EntityID = de.EntityID AND de.FwEntityID IN ({entityIds})"; + break; + default: + var rollupID = rollup.Replace(" ", string.Empty) + "ID"; + entityId = "department." + rollupID; + entityJoin = $@" + inner join fw.DimDepartment department on pes.HighestVariableCostDepartmentID = department.DepartmentID + AND {entityId} IN ({entityIds})"; + break; + } + + var serviceLineJoin = serviceLineInfo.ServiceLineJoin; + if (serviceLineInfo.ServiceLineTableName?.Equals("fw.viewDimStrataDecisionServiceLine", StringComparison.OrdinalIgnoreCase) == true) + { + serviceLineJoin = serviceLineJoin.Replace("fw.viewDimStrataDecisionServiceLine", "fw.DimStrataDecisionServiceLine", StringComparison.OrdinalIgnoreCase); + } + else if (serviceLineInfo.ServiceLineTableName?.Equals("fw.viewDimServiceLine", StringComparison.OrdinalIgnoreCase) == true) + { + serviceLineJoin = serviceLineJoin.Replace("fw.viewDimServiceLine", "fw.DimServiceLine", StringComparison.OrdinalIgnoreCase); + } + + var serviceLineColumn = ", " + serviceLineInfo.ServiceLineColumn; + + return $@" + WITH EncounterData AS ( + SELECT + pes.EncounterId, + pes.DischargeDateTime, + pes.PrimaryPhysicianID, + pes.MSDRGCaseTypeFamilyID, + pes.APRDRGCaseTypeFamilyID, + pes.LengthOfStay, + pes.NetRevenue, + pes.InsurancePlan1ID, + pes.SourceSystemID, + {entityId} AS EntityID{serviceLineColumn} ServiceLine_ID + FROM clientdss.FactPatientEncounterSummary pes{entityJoin}{serviceLineJoin} + INNER JOIN fw.DimPatientType pt ON pes.PatientTypeID = pt.PatientTypeID + INNER JOIN fw.DimPatientTypeRollup ptr ON pt.PatientTypeRollupID = ptr.PatientTypeRollupID AND ptr.Type = 'Inpatient' + WHERE pes.DischargeDateTime >= :StartDate AND pes.DischargeDateTime < :EndDate + {whereString} + AND pes.SourceSystemID IN ({sourceSystemIds}) + ), + QVIMetrics AS ( + SELECT + YEAR(ed.DischargeDateTime) AS YearId, + MONTH(ed.DischargeDateTime) AS MonthId, + ed.PrimaryPhysicianID AS PhysicianId, + ed.EntityID AS EntityId, + fin.FinancialClassID AS FinancialClassId, + ip.InsurancePlanID AS InsurancePlanId, + ed.ServiceLine_ID AS ServiceLineId, + ed.{caseTypeFamilyIdSqlColumn} AS CaseTypeFamilyId, + COUNT(1) AS TotalInpatientCases, + SUM(CASE WHEN qvili.IsPrimary IS NULL THEN 1 ELSE 0 END) AS TotalCasesWithoutQvi, + SUM(CASE WHEN qvili.IsPrimary IS NOT NULL THEN 1 ELSE 0 END) AS TotalCasesWithQvi, + COALESCE(SUM(CASE WHEN qvili.IsPrimary IS NULL THEN ed.LengthOfStay ELSE 0 END), 0) AS TotalPatientDaysWithoutQvi, + COALESCE(SUM(CASE WHEN qvili.IsPrimary IS NULL THEN ed.NetRevenue ELSE 0 END), 0) AS NetRevenueWithoutQvi, + COALESCE(SUM(CASE WHEN qvili.IsPrimary IS NOT NULL THEN ed.NetRevenue ELSE 0 END), 0) AS NetRevenueWithQvi, + COALESCE(SUM(CASE WHEN qvili.IsPrimary IS NULL THEN cost.VariableDirectCost ELSE 0 END), 0) AS TotalCostWithoutQvi, + COALESCE(SUM(CASE WHEN qvili.IsPrimary IS NOT NULL THEN cost.VariableDirectCost ELSE 0 END), 0) AS TotalCostWithQvi, + CAST(:IsBaseline AS BOOLEAN) AS IsBaseline + FROM EncounterData ed + INNER JOIN ( + SELECT EncounterId, + SUM(VariableDirectCost) AS VariableDirectCost + FROM cci.encounterchargecodecosts + GROUP BY EncounterId + ) cost ON ed.EncounterId = cost.EncounterId + INNER JOIN dss.DimInsurancePlan ip ON ed.InsurancePlan1ID = ip.InsurancePlanID + INNER JOIN clientdss.DimFinancialClass fin ON ip.FinancialClassID = fin.FinancialClassID + LEFT OUTER JOIN dss.FactPatientEncounterQualityVariationEvent qvili ON ed.EncounterId = qvili.EncounterId AND qvili.IsPrimary = 1 + GROUP BY + YEAR(ed.DischargeDateTime), MONTH(ed.DischargeDateTime), ed.PrimaryPhysicianID, ed.EntityID, fin.FinancialClassID, + ed.ServiceLine_ID, ed.{caseTypeFamilyIdSqlColumn}, ip.InsurancePlanID + ) + SELECT + :ConfigurationGuid, + YearId, + MonthId, + PhysicianId, + EntityId, + FinancialClassId, + InsurancePlanId, + ServiceLineId, + CaseTypeFamilyId, + TotalInpatientCases, + TotalCasesWithoutQvi, + TotalCasesWithQvi, + TotalPatientDaysWithoutQvi, + NetRevenueWithoutQvi, + NetRevenueWithQvi, + TotalCostWithoutQvi, + TotalCostWithQvi, + IsBaseline + FROM QVIMetrics"; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/QVIMetricService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/QVIMetricService.cs new file mode 100644 index 0000000..b06425f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/QVIMetricService.cs @@ -0,0 +1,125 @@ +using EFCore.BulkExtensions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.ServiceLines; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Metrics +{ + public class QviMetricService : IQviMetricService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext; + private readonly IConfigurationService _configurationService; + private readonly ILogger _logger; + + public QviMetricService( + IAsyncDbContextFactory dbContextFactory, + ISnowflakeDatabaseContext snowflakeDatabaseContext, + IConfigurationService configurationService, + ILogger logger) + { + _dbContextFactory = dbContextFactory; + _snowflakeDatabaseContext = snowflakeDatabaseContext; + _configurationService = configurationService; + _logger = logger; + } + + public async Task PopulateMetricsAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + + var config = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + + var entityIds = config.GetConfiguredEntityIds().ToCSVList(); + var sourceSystemIds = config.GetSourceSystemIds().ToCSVList(); + + if (string.IsNullOrWhiteSpace(entityIds) || string.IsNullOrWhiteSpace(sourceSystemIds)) + { + return 0; + } + + var serviceLineInformation = await ServiceLineInformation.GetServiceLineInformationAsync(dbContext, config, "pes", cancellationToken); + + var startDate = isBaseline + ? config.GetBaselineStartDate(OpportunityTypes.QualityVariation) + : config.GetTrackingStartDate(OpportunityTypes.QualityVariation); + + var endDate = isBaseline + ? config.BaselineEndDate(OpportunityTypes.QualityVariation) + : config.TrackingCurrentMonthEndDate(OpportunityTypes.QualityVariation); + + // Step 1: Query Snowflake for encounter data and metrics + var snowflakeResults = await _snowflakeDatabaseContext.QueryAsync( + QviMetricData.BuildQuery(entityIds, sourceSystemIds, config, serviceLineInformation), + "PopulateMetrics", + new + { + ConfigurationGuid = configurationGuid, + StartDate = startDate, + EndDate = endDate, + IsBaseline = isBaseline + }); + + var metricsList = snowflakeResults.ToList(); + + if (!metricsList.Any()) + { + return 0; + } + + //Step 2: Clear existing QviMetrics + await dbContext.QviMetrics + .Where(m => m.ConfigurationGuid == configurationGuid && m.IsBaseline == isBaseline) + .ExecuteDeleteAsync(cancellationToken); + + // Step 3: Batch insert + var qviMetrics = metricsList.Select(metric => new QviMetrics + { + ConfigurationGuid = configurationGuid, + FiscalYearId = metric.YearId, + FiscalMonthId = metric.MonthId, + PhysicianId = metric.PhysicianId, + EntityId = metric.EntityId, + FinancialClassId = metric.FinancialClassId, + InsurancePlanId = metric.InsurancePlanId, + ServiceLineId = metric.ServiceLineId, + CaseTypeFamilyId = metric.CaseTypeFamilyId, + InPatientCases = metric.TotalInpatientCases, + CasesWithoutQvi = metric.TotalCasesWithoutQvi, + CasesWithQvi = metric.TotalCasesWithQvi, + PatientDaysWithoutQvi = metric.TotalPatientDaysWithoutQvi, + NetRevenueWithoutQvi = metric.NetRevenueWithoutQvi, + NetRevenueWithQvi = metric.NetRevenueWithQvi, + CostWithoutQvi = metric.TotalCostWithoutQvi, + CostWithQvi = metric.TotalCostWithQvi, + IsBaseline = isBaseline + }).ToList(); + + try + { + await dbContext.BulkInsertAsync(qviMetrics, new BulkConfig + { + TrackingEntities = false, + BatchSize = 10000 + }, cancellationToken: cancellationToken); + + } + catch (Exception ex) + { + _logger.LogError(ex, "Error during BulkInsert of QVIMetrics"); + throw; + } + return qviMetrics.Count; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/QVIMetrics.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/QVIMetrics.cs new file mode 100644 index 0000000..4fd0d2e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Metrics/QVIMetrics.cs @@ -0,0 +1,69 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Metrics +{ + [Table("QVIMetrics", Schema = "cci")] + public class QviMetrics + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Column("RowID")] + public long RowId { get; set; } + + [Column("ConfigurationGUID")] + public Guid ConfigurationGuid { get; set; } + + [Column("FiscalYearID")] + public short FiscalYearId { get; set; } + + [Column("FiscalMonthID")] + public byte FiscalMonthId { get; set; } + + [Column("PhysicianID")] + public int PhysicianId { get; set; } + + [Column("EntityID")] + public int EntityId { get; set; } + + [Column("InPatientCases")] + public int InPatientCases { get; set; } + + [Column("CasesWithoutQVI")] + public int CasesWithoutQvi { get; set; } + + [Column("PatientDaysWithoutQVI")] + public int PatientDaysWithoutQvi { get; set; } + + [Column("NetRevenueWithoutQVI")] + public decimal NetRevenueWithoutQvi { get; set; } + + [Column("CostWithoutQVI")] + public decimal CostWithoutQvi { get; set; } + + [Column("IsBaseline")] + public bool IsBaseline { get; set; } + + [Column("ServiceLineID")] + public int ServiceLineId { get; set; } + + [Column("CaseTypeFamilyID")] + public int CaseTypeFamilyId { get; set; } + + [Column("FinancialClassID")] + public int FinancialClassId { get; set; } + + [Column("CasesWithQVI")] + public int CasesWithQvi { get; set; } + + [Column("CostWithQVI")] + public decimal CostWithQvi { get; set; } + + [Column("NetRevenueWithQVI")] + public decimal NetRevenueWithQvi { get; set; } + + [Column("InsurancePlanID")] + public int InsurancePlanId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/Filters/QVIFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/Filters/QVIFilterOptions.cs new file mode 100644 index 0000000..0f254a6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/Filters/QVIFilterOptions.cs @@ -0,0 +1,12 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities.Filters +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIFilterOptions + { + public IEnumerable Breakdowns { get; set; } + public IEnumerable QualityVariationEvents { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/Filters/QVIFilters.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/Filters/QVIFilters.cs new file mode 100644 index 0000000..5848798 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/Filters/QVIFilters.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities.Filters +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIFilters : FiltersOpportunity + { + public IEnumerable BreakdownIds { get; set; } + public IEnumerable QualityVariationEventIds { get; set; } + public int QVICases { get; set; } + + public QVIFilters() : base() + { + BreakdownIds = new List(); + QualityVariationEventIds = new List(); + QVICases = 0; + } + + public override IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = base.GetFilters(context).ToList(); + + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + filters.Add(m => m.OpportunityKey.Contains(OpportunitySearch) + || m.QualityVariationEventName.Contains(OpportunitySearch) + || m.BreakdownName.Contains(OpportunitySearch) + || m.QualityVariationEventName.Contains(OpportunitySearch)); + } + + if (QVICases > 0) + filters.Add(m => m.QVICases >= QVICases); + + if (BreakdownIds.Any()) + filters.Add(m => BreakdownIds.Contains(m.BreakdownId.ToString())); + + if (QualityVariationEventIds.Any()) + filters.Add(m => QualityVariationEventIds.Contains(m.QualityVariationEventId.ToString())); + + return filters; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/IQVIOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/IQVIOpportunityService.cs new file mode 100644 index 0000000..2a25687 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/IQVIOpportunityService.cs @@ -0,0 +1,17 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities.Filters; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public interface IQVIOpportunityService : IOpportunityService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + Task ExportAsync(SortOptions sortOptions, QVIFilters filters, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/QVIOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/QVIOpportunity.cs new file mode 100644 index 0000000..effa0f3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/QVIOpportunity.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Generics; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities +{ + [Table("viewQualityVariationItemOpportunity", Schema = "cci")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", + Justification = "Standard abbreviation")] + public class QVIOpportunity : Opportunity + { + public int QVICases { get; set; } + //move OpportunityId to Opportunity class after migration for opportunity tables + public long OpportunityId { get; set; } + public int BreakdownId { get; set; } + public string BreakdownName { get; set; } + public int Cases { get; set; } + public int QualityVariationEventId { get; set; } + public string QualityVariationEventName { get; set; } + [Precision(19, 4)] + public decimal QVIRate { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/QVIOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/QVIOpportunityService.cs new file mode 100644 index 0000000..691339f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/Opportunities/QVIOpportunityService.cs @@ -0,0 +1,110 @@ +using ClosedXML.Excel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities.Filters; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.Opportunities +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class QVIOpportunityService : OpportunityService, IQVIOpportunityService + { + private const int BREAKDOWN_COLUMN_INDEX = 6; + private readonly IConfigurationService _configurationService; + private readonly ILogger _logger; + + public QVIOpportunityService(IAsyncDbContextFactory dbContextFactory, CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IInitiativeService initiativeService, IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService, + IConfigurationService configurationService, + ILogger logger) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, initiativeService, updateFactOpportunitySavingsService) + { + _configurationService = configurationService; + _logger = logger; + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return null; + + var opportunityContext = dbContext.Set(); + var configuredOpportunities = opportunityContext.Where(vo => vo.ConfigurationGuid == configurationGuid); + + var breakdowns = configuredOpportunities + .Select(vo => new FilterMember { Id = vo.BreakdownId.ToString(), Text = vo.BreakdownName }) + .Distinct().OrderBy(oa => oa.Text); + + var qualityVariationEvents = configuredOpportunities + .Select(vo => new FilterMember { Id = vo.QualityVariationEventId.ToString(), Text = vo.QualityVariationEventName }) + .Distinct().OrderBy(oa => oa.Text); + + return new QVIFilterOptions() + { + Breakdowns = await breakdowns.ToListAsync(cancellationToken), + QualityVariationEvents = await qualityVariationEvents.ToListAsync(cancellationToken) + }; + } + + public async Task ExportAsync(SortOptions sortOptions, QVIFilters filters, CancellationToken cancellationToken) + { + var exportOptions = this.GetExportOptions(); + + // get data + var opportunities = await GetAllAsync(sortOptions, filters, cancellationToken); + var data = opportunities.Select(opp => new + { + Opportunity = opp.OpportunityKey, + opp.IdentifiedSavings, + QualityVariationCases = opp.QVICases, + QualityVariationRate = opp.QVIRate, + QualityVariationEvent = opp.QualityVariationEventName, + ServiceLine = opp.BreakdownName, + opp.Initiative, + IsHidden = opp.IsHidden ? 1 : 0, + opp.Note + }); + + // configure column formats + exportOptions.AddColumnOptions("Identified Savings", "Currency2Dec"); + exportOptions.AddColumnOptions("Quality Variation Cases", "Accounting"); + exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\""); + exportOptions.AddColumnOptions("Quality Variation Rate", "0%"); + + // special formatting for Breakdown column header + var workbook = ExportUtils.CreateExcelWorkbook(data, exportOptions); + + if (opportunities.Any()) + { + var breakdownName = await GetBreakdownNameAsync(opportunities.First().ConfigurationGuid, cancellationToken); + var ws = workbook.Worksheets.First(); + ws.Cell(1, BREAKDOWN_COLUMN_INDEX).Value = breakdownName.SplitCamelCase(); + } + + return workbook; + } + + protected async Task GetBreakdownNameAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + return dbContext?.Configurations + .FirstOrDefault(c => c.ConfigurationGuid == configurationGuid)? + .QVIOpportunityBreakdownName ?? "Breakdown"; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/EncounterInfo.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/EncounterInfo.cs new file mode 100644 index 0000000..15e0503 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/EncounterInfo.cs @@ -0,0 +1,95 @@ +using Strata.ContinuousImprovement.Biz.ServiceLines; +using System; +using CIConfiguration = Strata.ContinuousImprovement.Biz.Configuration.Configuration; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public class EncounterInfo + { + public long EncounterId { get; set; } + public string EncounterRecordNumber { get; set; } + public string DrgType { get; set; } + public decimal VariableDirectCost { get; set; } + public int LengthOfStay { get; set; } + public decimal NetRevenue { get; set; } + public int CaseTypeFamilyId { get; set; } + public int EntityId { get; set; } + public string Entity { get; set; } + public int PhysicianSpecialtyId { get; set; } + public string PhysicianSpecialty { get; set; } + public int ServiceLineId { get; set; } + public string ServiceLine { get; set; } + + public EncounterInfo() { } + + public static string BuildQuery(string entityIds, string sourceSystemIds, CIConfiguration config, ServiceLineInformation serviceLineInfo) + { + var rollup = config.DepartmentRollupGlobalId; + + string entity; + string entityJoin; + var whereEntityFilter = string.Empty; + + switch (rollup) + { + case "Location": + entity = "pes.LocationId"; + entityJoin = "INNER JOIN clientdss.DimLocation e ON e.LocationID = pes.LocationId"; + whereEntityFilter = $"AND pes.LocationId IN ({entityIds})"; + break; + case "": + entity = "e.EntityID"; + entityJoin = $@"INNER JOIN clientdss.DimEntity de ON pes.EntityID = de.EntityID AND de.FwEntityID IN ({entityIds}) + INNER JOIN fw.DimEntity e ON e.EntityID = de.FwEntityID"; + break; + default: + var sanitizedRollup = rollup.Replace(" ", string.Empty); + entity = $"department.{sanitizedRollup}ID"; + entityJoin = $@"INNER JOIN fw.DimDepartment department ON pes.HighestVariableCostDepartmentID = department.DepartmentID + AND department.{sanitizedRollup}ID IN ({entityIds}) + INNER JOIN fw.Dim{sanitizedRollup} e ON e.{sanitizedRollup}ID = department.{sanitizedRollup}ID"; + break; + } + + var serviceLineJoin = serviceLineInfo.ServiceLineJoin; + if (serviceLineInfo.ServiceLineTableName?.Equals("fw.viewDimStrataDecisionServiceLine", StringComparison.OrdinalIgnoreCase) == true) + { + serviceLineJoin = serviceLineJoin.Replace("fw.viewDimStrataDecisionServiceLine", "fw.DimStrataDecisionServiceLine", StringComparison.OrdinalIgnoreCase); + } + else if (serviceLineInfo.ServiceLineTableName?.Equals("fw.viewDimServiceLine", StringComparison.OrdinalIgnoreCase) == true) + { + serviceLineJoin = serviceLineJoin.Replace("fw.viewDimServiceLine", "fw.DimServiceLine", StringComparison.OrdinalIgnoreCase); + } + + var joinCondition = config.QualityVariationIsUsingAPRDRG + ? @"INNER JOIN dss.DimAPRDRG drg ON pes.ReportingAPRDRGID = drg.APRDRGID + INNER JOIN dss.CaseTypeFamily ctf ON pes.APRDRGCaseTypeFamilyID = ctf.CaseTypeFamilyID" + : @"INNER JOIN dss.DimMSDRG drg ON pes.ReportingDRGID = drg.MSDRGID + INNER JOIN dss.CaseTypeFamily ctf ON pes.MSDRGCaseTypeFamilyID = ctf.CaseTypeFamilyID"; + + return $@"SELECT pes.EncounterId, pes.EncounterRecordNumber, drg.Type AS DrgType, cost.VariableDirectCost, pes.LengthOfStay + , pes.NetRevenue, ctf.CaseTypeFamilyID AS CaseTypeFamilyId, CAST({entity} AS INT) AS EntityId, e.Name AS Entity + , physp.PhysicianSpecialtyID AS PhysicianSpecialtyId, physp.Name AS PhysicianSpecialty + , {serviceLineInfo.ServiceLineColumn} AS ServiceLineId, sl.{serviceLineInfo.ServiceLineNameAttribute} AS ServiceLine +FROM clientdss.FactPatientEncounterSummary pes + {entityJoin} + INNER JOIN ( + SELECT EncounterId, SUM(VariableDirectCost) AS VariableDirectCost + FROM cci.encounterchargecodecosts + GROUP BY EncounterId + ) cost ON pes.EncounterId = cost.EncounterId + INNER JOIN fw.DimPatientType pt ON pes.PatientTypeID = pt.PatientTypeID + INNER JOIN fw.DimPatientTypeRollup ptr ON pt.PatientTypeRollupID = ptr.PatientTypeRollupID + AND ptr.Type = 'Inpatient' + INNER JOIN dss.DimPhysician phy ON pes.PrimaryPhysicianID = phy.PhysicianID + INNER JOIN dss.DimPhysicianSpecialty physp ON phy.PhysicianSpecialtyID = physp.PhysicianSpecialtyID + {serviceLineJoin} + {joinCondition} +WHERE pes.DischargeDateTime >= :StartDate + AND pes.DischargeDateTime < :EndDate + {whereEntityFilter} + AND pes.EncounterId IN (SELECT EncounterId FROM dss.FactPatientEncounterQualityVariationEvent) + AND pes.SourceSystemID IN ({sourceSystemIds})"; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/INonQviSavingsService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/INonQviSavingsService.cs new file mode 100644 index 0000000..860e93f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/INonQviSavingsService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public interface INonQviSavingsService + { + Task> GetCaseTypeFamilyInfoAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken); + Task> GetDrgTypeInfoAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/IQviEncounterService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/IQviEncounterService.cs new file mode 100644 index 0000000..dc3d3d1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/IQviEncounterService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public interface IQviEncounterService + { + Task> GetEncounterInfoAsync(Guid configurationGuid, DateTime startDate, DateTime endDate, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/IQviLineItemSavingsService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/IQviLineItemSavingsService.cs new file mode 100644 index 0000000..90f363b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/IQviLineItemSavingsService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public interface IQviLineItemSavingsService + { + Task> GetCaseTypeFamilyInfoAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken); + Task> GetDrgTypeInfoAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken); + Task> GetEncounterSavingInfoAsync(Guid configurationGuid, Guid? opportunityGuid, bool? isBaseline, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/NonQVICaseTypeFamilyInfo.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/NonQVICaseTypeFamilyInfo.cs new file mode 100644 index 0000000..bddb207 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/NonQVICaseTypeFamilyInfo.cs @@ -0,0 +1,76 @@ +using System; +using CIConfiguration = Strata.ContinuousImprovement.Biz.Configuration.Configuration; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public class NonQviCaseTypeFamilyInfo + { + public int CaseTypeFamilyId { get; set; } + public decimal MedianVariableDirectCostPerCase { get; set; } + public int CaseCount { get; set; } + + public NonQviCaseTypeFamilyInfo() { } + + public NonQviCaseTypeFamilyInfo(int caseTypeFamilyId, double medianVariableDirectCostPerCase, int caseCount) + { + CaseTypeFamilyId = caseTypeFamilyId; + MedianVariableDirectCostPerCase = Convert.ToDecimal(medianVariableDirectCostPerCase); + CaseCount = caseCount; + } + + public static string BuildQuery(string entityIds, string sourceSystemIds, CIConfiguration config) + { + var caseTypeFamilyIdColumn = config.GetCaseTypeFamilyIdSqlColumn(); + var rollup = config.DepartmentRollupGlobalId; + + string entityJoin; + var whereString = ""; + + switch (rollup) + { + case "Location": + entityJoin = ""; + whereString = $"AND pes.LocationID IN ({entityIds})"; + break; + case "": + entityJoin = $@"INNER JOIN clientdss.DimEntity de ON pes.EntityID = de.EntityID + AND de.FwEntityID IN ({entityIds})"; + break; + default: + var rollupId = rollup.Replace(" ", string.Empty) + "ID"; + var entityId = "department." + rollupId; + entityJoin = $@"INNER JOIN fw.DimDepartment department ON pes.HighestVariableCostDepartmentID = department.DepartmentID + AND {entityId} IN ({entityIds})"; + break; + } + + return $@"WITH EncounterData AS ( + SELECT DISTINCT + pes.{caseTypeFamilyIdColumn} AS CaseTypeFamilyId, + pes.EncounterId, + PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY cost.VariableDirectCost) + OVER (PARTITION BY pes.{caseTypeFamilyIdColumn}) AS MedianVariableDirectCostPerCase + FROM clientdss.FactPatientEncounterSummary pes + {entityJoin} + INNER JOIN ( + SELECT EncounterId, SUM(VariableDirectCost) AS VariableDirectCost + FROM cci.encounterchargecodecosts + GROUP BY EncounterId + ) cost ON pes.EncounterId = cost.EncounterId + INNER JOIN fw.DimPatientType pt ON pes.PatientTypeID = pt.PatientTypeID + INNER JOIN fw.DimPatientTypeRollup ptr ON pt.PatientTypeRollupID = ptr.PatientTypeRollupID + AND ptr.Type = 'Inpatient' + WHERE pes.EncounterId NOT IN ( + SELECT EncounterId FROM dss.FactPatientEncounterQualityVariationEvent + ) + {whereString} + AND pes.DischargeDateTime >= :StartDate + AND pes.DischargeDateTime < :EndDate + AND pes.SourceSystemID IN ({sourceSystemIds}) +) +SELECT CaseTypeFamilyId, MedianVariableDirectCostPerCase, COUNT(DISTINCT EncounterId) AS CaseCount +FROM EncounterData +GROUP BY CaseTypeFamilyId, MedianVariableDirectCostPerCase"; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/NonQVIDrgTypeInfo.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/NonQVIDrgTypeInfo.cs new file mode 100644 index 0000000..02029a8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/NonQVIDrgTypeInfo.cs @@ -0,0 +1,79 @@ +using System; +using CIConfiguration = Strata.ContinuousImprovement.Biz.Configuration.Configuration; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public class NonQviDrgTypeInfo + { + public string DrgType { get; set; } + public decimal MedianVariableDirectCostPerCase { get; set; } + public int CaseCount { get; set; } + + public NonQviDrgTypeInfo() { } + + public NonQviDrgTypeInfo(string drgType, double medianVariableDirectCostPerCase, int caseCount) + { + DrgType = drgType; + MedianVariableDirectCostPerCase = Convert.ToDecimal(medianVariableDirectCostPerCase); + CaseCount = caseCount; + } + + public static string BuildQuery(string entityIds, string sourceSystemIds, CIConfiguration config) + { + var joinCondition = config.QualityVariationIsUsingAPRDRG + ? "INNER JOIN dss.DimAPRDRG drg ON pes.ReportingAPRDRGID = drg.APRDRGID" + : "INNER JOIN dss.DimMSDRG drg ON pes.ReportingDRGID = drg.MSDRGID"; + + var rollup = config.DepartmentRollupGlobalId; + string entityJoin; + var whereString = ""; + + switch (rollup) + { + case "Location": + entityJoin = ""; + whereString = $"AND pes.LocationID IN ({entityIds})"; + break; + case "": + entityJoin = $@"INNER JOIN clientdss.DimEntity de ON pes.EntityID = de.EntityID + AND de.FwEntityID IN ({entityIds})"; + break; + default: + var rollupId = rollup.Replace(" ", string.Empty) + "ID"; + var entityId = "department." + rollupId; + entityJoin = $@"INNER JOIN fw.DimDepartment department ON pes.HighestVariableCostDepartmentID = department.DepartmentID + AND {entityId} IN ({entityIds})"; + break; + } + + return $@"WITH EncounterData AS ( + SELECT DISTINCT + drg.Type AS DRGType, + pes.EncounterId, + PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY cost.VariableDirectCost) + OVER (PARTITION BY drg.Type) AS MedianVariableDirectCostPerCase + FROM clientdss.FactPatientEncounterSummary pes + {entityJoin} + INNER JOIN ( + SELECT EncounterId, SUM(VariableDirectCost) AS VariableDirectCost + FROM cci.encounterchargecodecosts + GROUP BY EncounterId + ) cost ON pes.EncounterId = cost.EncounterId + INNER JOIN fw.DimPatientType pt ON pes.PatientTypeID = pt.PatientTypeID + INNER JOIN fw.DimPatientTypeRollup ptr ON pt.PatientTypeRollupID = ptr.PatientTypeRollupID + AND ptr.Type = 'Inpatient' + {joinCondition} + WHERE pes.EncounterId NOT IN ( + SELECT EncounterId FROM dss.FactPatientEncounterQualityVariationEvent + ) + {whereString} + AND pes.DischargeDateTime >= :StartDate + AND pes.DischargeDateTime < :EndDate + AND pes.SourceSystemID IN ({sourceSystemIds}) +) +SELECT DRGType, MedianVariableDirectCostPerCase, COUNT(DISTINCT EncounterId) AS CaseCount +FROM EncounterData +GROUP BY DRGType, MedianVariableDirectCostPerCase"; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/NonQviSavingsService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/NonQviSavingsService.cs new file mode 100644 index 0000000..990c340 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/NonQviSavingsService.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public class NonQviSavingsService : INonQviSavingsService + { + private readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext; + private readonly IConfigurationService _configurationService; + private readonly ILogger _logger; + + public NonQviSavingsService( + ISnowflakeDatabaseContext snowflakeDatabaseContext, + IConfigurationService configurationService, + ILogger logger) + { + _snowflakeDatabaseContext = snowflakeDatabaseContext; + _configurationService = configurationService; + _logger = logger; + } + + public async Task> GetCaseTypeFamilyInfoAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken) + { + try + { + var config = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + var entityIds = config.GetConfiguredEntityIds().ToCSVList(); + var sourceSystemIds = config.GetSourceSystemIds().ToCSVList(); + if (string.IsNullOrWhiteSpace(entityIds) || string.IsNullOrWhiteSpace(sourceSystemIds)) return Enumerable.Empty(); + + var (startDate, endDate) = GetDateRange(config, isBaseline); + + return await _snowflakeDatabaseContext.QueryAsync( + NonQviCaseTypeFamilyInfo.BuildQuery(entityIds, sourceSystemIds, config), + nameof(GetCaseTypeFamilyInfoAsync), + new { StartDate = startDate, EndDate = endDate }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error loading NonQVI case type family info for configuration {ConfigurationGuid}", configurationGuid); + throw; + } + } + + public async Task> GetDrgTypeInfoAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken) + { + try + { + var config = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + var entityIds = config.GetConfiguredEntityIds().ToCSVList(); + var sourceSystemIds = config.GetSourceSystemIds().ToCSVList(); + if (string.IsNullOrWhiteSpace(entityIds) || string.IsNullOrWhiteSpace(sourceSystemIds)) return Enumerable.Empty(); + + var (startDate, endDate) = GetDateRange(config, isBaseline); + + return await _snowflakeDatabaseContext.QueryAsync( + NonQviDrgTypeInfo.BuildQuery(entityIds, sourceSystemIds, config), + nameof(GetDrgTypeInfoAsync), + new { StartDate = startDate, EndDate = endDate }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error loading NonQVI DRG type info for configuration {ConfigurationGuid}", configurationGuid); + throw; + } + } + + private static (DateTime startDate, DateTime endDate) GetDateRange(Configuration.Configuration config, bool isBaseline) => + isBaseline + ? (config.GetBaselineStartDate(OpportunityTypes.QualityVariation), config.BaselineEndDate(OpportunityTypes.QualityVariation)) + : (config.GetTrackingStartDate(OpportunityTypes.QualityVariation), config.TrackingCurrentMonthEndDate(OpportunityTypes.QualityVariation)); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviEncounterService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviEncounterService.cs new file mode 100644 index 0000000..21e3df0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviEncounterService.cs @@ -0,0 +1,59 @@ +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.ServiceLines; +using Strata.ContinuousImprovement.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.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public class QviEncounterService : IQviEncounterService + { + private readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext; + private readonly IConfigurationService _configurationService; + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ILogger _logger; + + public QviEncounterService( + ISnowflakeDatabaseContext snowflakeDatabaseContext, + IConfigurationService configurationService, + IAsyncDbContextFactory dbContextFactory, + ILogger logger) + { + _snowflakeDatabaseContext = snowflakeDatabaseContext; + _configurationService = configurationService; + _dbContextFactory = dbContextFactory; + _logger = logger; + } + + public async Task> GetEncounterInfoAsync(Guid configurationGuid, DateTime startDate, DateTime endDate, CancellationToken cancellationToken) + { + try + { + var config = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + var entityIds = config.GetConfiguredEntityIds().ToCSVList(); + var sourceSystemIds = config.GetSourceSystemIds().ToCSVList(); + if (string.IsNullOrWhiteSpace(entityIds) || string.IsNullOrWhiteSpace(sourceSystemIds)) return Enumerable.Empty(); + + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + var serviceLineInfo = await ServiceLineInformation.GetServiceLineInformationAsync(dbContext, config, "pes", cancellationToken); + + return await _snowflakeDatabaseContext.QueryAsync( + EncounterInfo.BuildQuery(entityIds, sourceSystemIds, config, serviceLineInfo), + nameof(GetEncounterInfoAsync), + new { StartDate = startDate, EndDate = endDate }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error loading encounter info for configuration {ConfigurationGuid}", configurationGuid); + throw; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemCaseTypeFamilyInfo.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemCaseTypeFamilyInfo.cs new file mode 100644 index 0000000..2c653a0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemCaseTypeFamilyInfo.cs @@ -0,0 +1,92 @@ +using System; +using CIConfiguration = Strata.ContinuousImprovement.Biz.Configuration.Configuration; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public class QviLineItemCaseTypeFamilyInfo + { + public int QualityVariationEventId { get; set; } + public int CaseTypeFamilyId { get; set; } + public decimal MedianVariableDirectCostPerCase { get; set; } + public int CaseCount { get; set; } + + public QviLineItemCaseTypeFamilyInfo() { } + + public QviLineItemCaseTypeFamilyInfo(int qualityVariationEventId, int caseTypeFamilyId, double medianVariableDirectCostPerCase, int caseCount) + { + QualityVariationEventId = qualityVariationEventId; + CaseTypeFamilyId = caseTypeFamilyId; + MedianVariableDirectCostPerCase = Convert.ToDecimal(medianVariableDirectCostPerCase); + CaseCount = caseCount; + } + + public static string BuildQuery(string entityIds, string sourceSystemIds, CIConfiguration config) + { + var caseTypeFamilyIdColumn = config.GetCaseTypeFamilyIdSqlColumn(); + + var rollup = config.DepartmentRollupGlobalId; + string entityJoin; + var whereString = ""; + + switch (rollup) + { + case "Location": + entityJoin = ""; + whereString = $"AND pes.LocationID IN ({entityIds})"; + break; + case "": + entityJoin = $@"INNER JOIN clientdss.DimEntity de ON pes.EntityID = de.EntityID + AND de.FwEntityID IN ({entityIds})"; + break; + default: + var rollupId = rollup.Replace(" ", string.Empty) + "ID"; + var entityId = "department." + rollupId; + entityJoin = $@"INNER JOIN fw.DimDepartment department ON pes.HighestVariableCostDepartmentID = department.DepartmentID + AND {entityId} IN ({entityIds})"; + break; + } + + return $@"WITH EncounterData AS ( + SELECT + pes.EncounterId, + cost.VariableDirectCost, + pes.{caseTypeFamilyIdColumn} AS CaseTypeFamilyID + FROM clientdss.FactPatientEncounterSummary pes + {entityJoin} + INNER JOIN ( + SELECT EncounterId, SUM(VariableDirectCost) AS VariableDirectCost + FROM cci.encounterchargecodecosts + GROUP BY EncounterId + ) cost ON pes.EncounterId = cost.EncounterId + INNER JOIN fw.DimPatientType pt ON pes.PatientTypeID = pt.PatientTypeID + INNER JOIN fw.DimPatientTypeRollup ptr ON pt.PatientTypeRollupID = ptr.PatientTypeRollupID + AND ptr.Type = 'Inpatient' + LEFT JOIN dss.FactPatientEncounterQualityVariationEvent eqvli ON pes.EncounterId = eqvli.EncounterId + WHERE pes.DischargeDateTime >= :StartDate + AND pes.DischargeDateTime < :EndDate + {whereString} + AND pes.SourceSystemID IN ({sourceSystemIds}) + GROUP BY pes.EncounterId, cost.VariableDirectCost, pes.{caseTypeFamilyIdColumn} + HAVING COUNT(DISTINCT eqvli.QualityVariationEventID) = 1 +), +QVICostData AS ( + SELECT + eqvli.QualityVariationEventID, + ed.CaseTypeFamilyID, + PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ed.VariableDirectCost) + OVER (PARTITION BY eqvli.QualityVariationEventID, ed.CaseTypeFamilyID) AS MedianVariableDirectCostPerCase, + ed.EncounterId + FROM EncounterData ed + INNER JOIN dss.FactPatientEncounterQualityVariationEvent eqvli ON eqvli.EncounterId = ed.EncounterId + AND eqvli.IsPrimary = 1 +) +SELECT + qcd.QualityVariationEventID, + qcd.CaseTypeFamilyID, + qcd.MedianVariableDirectCostPerCase, + COUNT(DISTINCT qcd.EncounterId) AS CaseCount +FROM QVICostData qcd +GROUP BY qcd.QualityVariationEventID, qcd.CaseTypeFamilyID, qcd.MedianVariableDirectCostPerCase"; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemDrgTypeInfo.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemDrgTypeInfo.cs new file mode 100644 index 0000000..6d32170 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemDrgTypeInfo.cs @@ -0,0 +1,96 @@ +using System; +using CIConfiguration = Strata.ContinuousImprovement.Biz.Configuration.Configuration; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public class QviLineItemDrgTypeInfo + { + public int QualityVariationEventId { get; set; } + public string DrgType { get; set; } + public decimal MedianVariableDirectCostPerCase { get; set; } + public int CaseCount { get; set; } + + public QviLineItemDrgTypeInfo() { } + + public QviLineItemDrgTypeInfo(int qualityVariationEventId, string drgType, double medianVariableDirectCostPerCase, int caseCount) + { + QualityVariationEventId = qualityVariationEventId; + DrgType = drgType; + MedianVariableDirectCostPerCase = Convert.ToDecimal(medianVariableDirectCostPerCase); + CaseCount = caseCount; + } + + public static string BuildQuery(string entityIds, string sourceSystemIds, CIConfiguration config) + { + var rollup = config.DepartmentRollupGlobalId; + string entityJoin; + var whereString = ""; + + switch (rollup) + { + case "Location": + entityJoin = ""; + whereString = $"AND pes.LocationID IN ({entityIds})"; + break; + case "": + entityJoin = $@"INNER JOIN clientdss.DimEntity de ON pes.EntityID = de.EntityID + AND de.FwEntityID IN ({entityIds})"; + break; + default: + var rollupId = rollup.Replace(" ", string.Empty) + "ID"; + var entityId = "department." + rollupId; + entityJoin = $@"INNER JOIN fw.DimDepartment department ON pes.HighestVariableCostDepartmentID = department.DepartmentID + AND {entityId} IN ({entityIds})"; + break; + } + + var joinCondition = config.QualityVariationIsUsingAPRDRG + ? "INNER JOIN dss.DimAPRDRG drg ON pes.ReportingAPRDRGID = drg.APRDRGID" + : "INNER JOIN dss.DimMSDRG drg ON pes.ReportingDRGID = drg.MSDRGID"; + + return $@"WITH EncounterData AS ( + SELECT + pes.EncounterId, + cost.VariableDirectCost, + drg.Type AS DRGType + FROM clientdss.FactPatientEncounterSummary pes + {entityJoin} + INNER JOIN ( + SELECT EncounterId, SUM(VariableDirectCost) AS VariableDirectCost + FROM cci.encounterchargecodecosts + GROUP BY EncounterId + ) cost ON pes.EncounterId = cost.EncounterId + INNER JOIN fw.DimPatientType pt ON pes.PatientTypeID = pt.PatientTypeID + INNER JOIN fw.DimPatientTypeRollup ptr ON pt.PatientTypeRollupID = ptr.PatientTypeRollupID + AND ptr.Type = 'Inpatient' + {joinCondition} + LEFT JOIN dss.FactPatientEncounterQualityVariationEvent eqvli ON pes.EncounterId = eqvli.EncounterId + WHERE pes.DischargeDateTime >= :StartDate + AND pes.DischargeDateTime < :EndDate + {whereString} + AND pes.SourceSystemID IN ({sourceSystemIds}) + GROUP BY pes.EncounterId, cost.VariableDirectCost, drg.Type + HAVING COUNT(DISTINCT eqvli.QualityVariationEventID) = 1 +), +QVICostData AS ( + SELECT + eqvli.QualityVariationEventID, + ed.DRGType, + PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ed.VariableDirectCost) + OVER (PARTITION BY eqvli.QualityVariationEventID, ed.DRGType) AS MedianVariableDirectCostPerCase, + ed.EncounterId + FROM EncounterData ed + INNER JOIN dss.FactPatientEncounterQualityVariationEvent eqvli ON eqvli.EncounterId = ed.EncounterId + AND eqvli.IsPrimary = 1 + WHERE ed.EncounterId IN (SELECT EncounterId FROM EncounterData) +) +SELECT + qcd.QualityVariationEventID, + qcd.DRGType, + qcd.MedianVariableDirectCostPerCase, + COUNT(DISTINCT qcd.EncounterId) AS CaseCount +FROM QVICostData qcd +GROUP BY qcd.QualityVariationEventID, qcd.DRGType, qcd.MedianVariableDirectCostPerCase"; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemEncounterSavingInfo.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemEncounterSavingInfo.cs new file mode 100644 index 0000000..15421e5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemEncounterSavingInfo.cs @@ -0,0 +1,87 @@ +using System; +using CIConfiguration = Strata.ContinuousImprovement.Biz.Configuration.Configuration; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public class QviLineItemEncounterSavingInfo + { + public string EncounterRecordNumber { get; set; } + public decimal Savings { get; set; } + public bool IsBaseline { get; set; } + public int PhysicianID { get; set; } + public string PhysicianName { get; set; } + public int PhysicianSpecialtyID { get; set; } + public string PhysicianSpecialty { get; set; } + public int EntityID { get; set; } + public string Entity { get; set; } + public int FinancialClassID { get; set; } + public int PayorID { get; set; } + public int ContractGroupingID { get; set; } + public int PayorGroupID { get; set; } + public int InsurancePlanID { get; set; } + public decimal NetRevenue { get; set; } + public int LengthOfStay { get; set; } + public decimal PesVariableDirectCost { get; set; } + public decimal TotalCost { get; set; } + public DateTime DischargeDateTime { get; set; } + public int CaseTypeFamilyID { get; set; } + public decimal VariableDirectCost { get; set; } + public string ServiceLine { get; set; } + public int ServiceLineID { get; set; } + public string CaseTypeFamily { get; set; } + + public QviLineItemEncounterSavingInfo() { } + + public static string BuildQuery(CIConfiguration config, Guid? opportunityGuid = null, bool? isBaseline = null) + { + var opportunityFilter = opportunityGuid.HasValue ? "\n AND OBJ.OpportunityGUID = :OpportunityGUID" : string.Empty; + var isBaselineFilter = isBaseline.HasValue ? "\n AND OBJ.IsBaseline = :IsBaseline" : string.Empty; + var caseTypeFamilyIdColumn = config.GetCaseTypeFamilyIdSqlColumn(); + + return $@"SELECT OBJ.EncounterRecordNumber, + OBJ.Savings, + OBJ.IsBaseline, + p.PhysicianID, + p.FullName AS PhysicianName, + OBJ.PhysicianSpecialtyID, + OBJ.PhysicianSpecialty, + OBJ.EntityID, + OBJ.Entity, + fin.FinancialClassID, + ip.PayorID, + ip.ContractGroupingID, + py.PayorGroupID, + ip.InsurancePlanID, + pes.NetRevenue, + pes.LengthOfStay, + cost.VariableDirectCost AS PesVariableDirectCost, + (cost.VariableDirectCost + cost.VariableIndirectCost + cost.FixedDirectCost + cost.FixedIndirectCost) AS TotalCost, + pes.DischargeDateTime, + ctf.CaseTypeFamilyID, + OBJ.VariableDirectCost, + OBJ.ServiceLine, + OBJ.ServiceLineID, + ctf.Name AS CaseTypeFamily + FROM cci.QVILineItemEncounterSaving OBJ + INNER JOIN cci.QVILineItemOpportunity qvi ON qvi.OpportunityGUID = OBJ.OpportunityGUID + INNER JOIN clientdss.FactPatientEncounterSummary pes ON OBJ.EncounterId = pes.EncounterId + INNER JOIN ( + SELECT EncounterId, + SUM(VariableDirectCost) AS VariableDirectCost, + SUM(VariableIndirectCost) AS VariableIndirectCost, + SUM(FixedDirectCost) AS FixedDirectCost, + SUM(FixedIndirectCost) AS FixedIndirectCost + FROM cci.encounterchargecodecosts + GROUP BY EncounterId + ) cost ON pes.EncounterId = cost.EncounterId + INNER JOIN dss.DimInsurancePlan ip ON pes.InsurancePlan1ID = ip.InsurancePlanID + INNER JOIN fw.DimPayor py ON ip.PayorID = py.PayorID + INNER JOIN fw.DimPayorGroup pyg ON py.PayorGroupID = pyg.PayorGroupID + INNER JOIN clientdss.DimFinancialClass fin ON ip.FinancialClassID = fin.FinancialClassID + INNER JOIN dss.DimPhysician p ON pes.PrimaryPhysicianId = p.PhysicianID + INNER JOIN clientdss.DimEntity de ON pes.EntityID = de.EntityID + INNER JOIN dss.CaseTypeFamily ctf ON pes.{caseTypeFamilyIdColumn} = ctf.CaseTypeFamilyID + WHERE qvi.ConfigurationGUID = :ConfigurationGUID{opportunityFilter}{isBaselineFilter}"; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemSavingsService.cs b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemSavingsService.cs new file mode 100644 index 0000000..6b5b96e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/QviLineItemSavingsService.cs @@ -0,0 +1,98 @@ +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.QualityVariation.QVI +{ + public class QviLineItemSavingsService : IQviLineItemSavingsService + { + private readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext; + private readonly IConfigurationService _configurationService; + private readonly ILogger _logger; + + public QviLineItemSavingsService( + ISnowflakeDatabaseContext snowflakeDatabaseContext, + IConfigurationService configurationService, + ILogger logger) + { + _snowflakeDatabaseContext = snowflakeDatabaseContext; + _configurationService = configurationService; + _logger = logger; + } + + public async Task> GetCaseTypeFamilyInfoAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken) + { + try + { + var config = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + var entityIds = config.GetConfiguredEntityIds().ToCSVList(); + var sourceSystemIds = config.GetSourceSystemIds().ToCSVList(); + if (string.IsNullOrWhiteSpace(entityIds) || string.IsNullOrWhiteSpace(sourceSystemIds)) return Enumerable.Empty(); + + var (startDate, endDate) = GetDateRange(config, isBaseline); + + return await _snowflakeDatabaseContext.QueryAsync( + QviLineItemCaseTypeFamilyInfo.BuildQuery(entityIds, sourceSystemIds, config), + nameof(GetCaseTypeFamilyInfoAsync), + new { StartDate = startDate, EndDate = endDate }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error loading QVI line item case type family info for configuration {ConfigurationGuid}", configurationGuid); + throw; + } + } + + public async Task> GetDrgTypeInfoAsync(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken) + { + try + { + var config = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + var entityIds = config.GetConfiguredEntityIds().ToCSVList(); + var sourceSystemIds = config.GetSourceSystemIds().ToCSVList(); + if (string.IsNullOrWhiteSpace(entityIds) || string.IsNullOrWhiteSpace(sourceSystemIds)) return Enumerable.Empty(); + + var (startDate, endDate) = GetDateRange(config, isBaseline); + + return await _snowflakeDatabaseContext.QueryAsync( + QviLineItemDrgTypeInfo.BuildQuery(entityIds, sourceSystemIds, config), + nameof(GetDrgTypeInfoAsync), + new { StartDate = startDate, EndDate = endDate }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error loading QVI line item DRG type info for configuration {ConfigurationGuid}", configurationGuid); + throw; + } + } + + public async Task> GetEncounterSavingInfoAsync(Guid configurationGuid, Guid? opportunityGuid, bool? isBaseline, CancellationToken cancellationToken) + { + try + { + var config = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + return await _snowflakeDatabaseContext.QueryAsync( + QviLineItemEncounterSavingInfo.BuildQuery(config, opportunityGuid, isBaseline), + nameof(GetEncounterSavingInfoAsync), + new { ConfigurationGUID = configurationGuid, OpportunityGUID = opportunityGuid, IsBaseline = isBaseline }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error loading QVI line item encounter saving info for configuration {ConfigurationGuid}", configurationGuid); + throw; + } + } + + private static (DateTime startDate, DateTime endDate) GetDateRange(Configuration.Configuration config, bool isBaseline) => + isBaseline + ? (config.GetBaselineStartDate(OpportunityTypes.QualityVariation), config.BaselineEndDate(OpportunityTypes.QualityVariation)) + : (config.GetTrackingStartDate(OpportunityTypes.QualityVariation), config.TrackingCurrentMonthEndDate(OpportunityTypes.QualityVariation)); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/quality-variation.md b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/quality-variation.md new file mode 100644 index 0000000..42e0ccf --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/QualityVariation/QVI/quality-variation.md @@ -0,0 +1,83 @@ +# QVI — Quality Variation Index + +Data access layer for analyzing inpatient quality variation events. Provides Snowflake-backed services that compare QVI encounter costs against a non-QVI benchmark population to surface savings opportunities. + +## Services + +| Interface | Implementation | Purpose | +|-----------|---------------|---------| +| `IQviEncounterService` | `QviEncounterService` | Per-encounter details for QVI events within a date range | +| `IQviLineItemSavingsService` | `QviLineItemSavingsService` | Savings analysis scoped to individual QVI line item opportunities | +| `INonQviSavingsService` | `NonQviSavingsService` | Benchmark metrics for encounters with **no** QVI events | + +All three services follow the same call pattern: + +1. Load the CI configuration via `IConfigurationService` +2. Extract entity IDs and source system IDs from the configuration +3. Resolve the date range (baseline or tracking period) for `OpportunityTypes.QualityVariation` +4. Execute a generated SQL query against `ISnowflakeDatabaseContext` + +--- + +## Query Models + +Each model class owns its own SQL generation via a static `BuildQuery(...)` method. The generated SQL is parameterized (`:StartDate`, `:EndDate`, etc.) and executed by the service. + +### Non-QVI benchmark models + +| Class | Groups by | Key detail | +|-------|-----------|-----------| +| `NonQviCaseTypeFamilyInfo` | Case type family | Excludes encounters in `FactPatientEncounterQualityVariationEvent` | +| `NonQviDrgTypeInfo` | DRG type (APR-DRG or MS-DRG) | Same exclusion; joins `DimAPRDRG` or `DimMSDRG` based on configuration | + +Both compute `MedianVariableDirectCostPerCase` using `PERCENTILE_CONT(0.5)` over aggregated charge code costs. + +### QVI line item models + +| Class | Groups by | Key detail | +|-------|-----------|-----------| +| `QviLineItemCaseTypeFamilyInfo` | QVI event + case type family | Only encounters with **exactly one** QVI event (`HAVING COUNT(DISTINCT QualityVariationEventID) = 1`) | +| `QviLineItemDrgTypeInfo` | QVI event + DRG type | Same single-event filter; APR-DRG or MS-DRG join based on configuration | +| `QviLineItemEncounterSavingInfo` | One row per encounter | Joins `QVILineItemEncounterSaving` with full financial and clinical context; supports optional `opportunityGuid` and `isBaseline` filters | + +### Encounter detail model + +`EncounterInfo` — returned by `IQviEncounterService`. One row per encounter that has at least one QVI event. Includes physician specialty, service line, entity, DRG type, variable direct cost, LOS, and net revenue. + +--- + +## Entity Scoping + +All `BuildQuery` methods branch on `config.DepartmentRollupGlobalId` to apply the correct entity filter: + +| `DepartmentRollupGlobalId` | Filter strategy | +|---------------------------|----------------| +| `"Location"` | `pes.LocationID IN (entityIds)` | +| `""` (empty) | Join `clientdss.DimEntity` → `fw.DimEntity` on `FwEntityID` | +| Any other value | Join `fw.DimDepartment` → `fw.Dim{Rollup}` on the rollup ID column | + +--- + +## DRG System + +`config.QualityVariationIsUsingAPRDRG` switches all DRG joins and case type family column lookups between APR-DRG (`dss.DimAPRDRG`, `APRDRGCaseTypeFamilyID`) and MS-DRG (`dss.DimMSDRG`, `MSDRGCaseTypeFamilyID`). + +--- + +## Service Line Handling (`QviEncounterService`) + +`QviEncounterService` is the only service that also queries the Jazz SQL Server database. It calls `ServiceLineInformation.GetServiceLineInformationAsync` to resolve the correct service line view/table name, then substitutes view names with base table names before executing the query against Snowflake. + +--- + +## Key Snowflake Tables + +| Schema | Table | Role | +|--------|-------|------| +| `clientdss` | `FactPatientEncounterSummary` | Core encounter fact table | +| `cci` | `encounterchargecodecosts` | Charge code cost rollup per encounter | +| `dss` | `FactPatientEncounterQualityVariationEvent` | QVI event membership | +| `cci` | `QVILineItemEncounterSaving` | Pre-computed savings rows per encounter | +| `cci` | `QVILineItemOpportunity` | QVI opportunity metadata, scoped by `ConfigurationGUID` | +| `dss` | `CaseTypeFamily` | Case type family dimension | +| `dss` | `DimAPRDRG` / `DimMSDRG` | DRG type dimensions | diff --git a/src/Strata.ContinuousImprovement.Biz/Replication/IPostgresReplicationService.cs b/src/Strata.ContinuousImprovement.Biz/Replication/IPostgresReplicationService.cs new file mode 100644 index 0000000..ee9f522 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Replication/IPostgresReplicationService.cs @@ -0,0 +1,13 @@ +using Strata.ContinuousImprovement.Biz.Replication.Models; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Replication +{ + public interface IPostgresReplicationService + { + Task Replicate(List> entitiesByClient, string table, CancellationToken cancellationToken) where T1 : IPostgresReplicable; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Replication/Models/DimExplorationPopulation.cs b/src/Strata.ContinuousImprovement.Biz/Replication/Models/DimExplorationPopulation.cs new file mode 100644 index 0000000..397db2b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Replication/Models/DimExplorationPopulation.cs @@ -0,0 +1,12 @@ +namespace Strata.ContinuousImprovement.Biz.Replication.Models +{ + public class DimExplorationPopulation + { + public long ExplorationPopulationId { get; set; } + public string Name { get; set; } + public int CaseTypeFamilyId { get; set; } + public string CostDriver { get; set; } + public int TotalEncounters { get; set; } + public decimal TotalVariation { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Replication/Models/FactExplorationPopulationEncounter.cs b/src/Strata.ContinuousImprovement.Biz/Replication/Models/FactExplorationPopulationEncounter.cs new file mode 100644 index 0000000..172620e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Replication/Models/FactExplorationPopulationEncounter.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.Replication.Models +{ + public class FactExplorationPopulationEncounter + { + public long ExplorationPopulationId { get; set; } + public long EncounterId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Replication/Models/IPostgresReplicable.cs b/src/Strata.ContinuousImprovement.Biz/Replication/Models/IPostgresReplicable.cs new file mode 100644 index 0000000..5dfa6b3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Replication/Models/IPostgresReplicable.cs @@ -0,0 +1,7 @@ +namespace Strata.ContinuousImprovement.Biz.Replication.Models +{ + public interface IPostgresReplicable + { + T MapToSnowflakeModel(); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Replication/PostgresReplicationService.cs b/src/Strata.ContinuousImprovement.Biz/Replication/PostgresReplicationService.cs new file mode 100644 index 0000000..a0ced76 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Replication/PostgresReplicationService.cs @@ -0,0 +1,100 @@ +using CsvHelper; +using CsvHelper.Configuration; +using Microsoft.Extensions.Logging; +using Strata.ApiCommunication.Http.Exceptions; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Replication.Models; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Id.Client; +using Strata.Replication.Client; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Replication +{ + public class PostgresReplicationService : IPostgresReplicationService + { + private const string STAGE_NAME = "@cci.POSTGRES_REPLICATION_DATA"; + private readonly IIdServiceClient _idServiceClient; + private readonly IReplicationServiceClient _replicationService; + private readonly ISnowflakeDatabaseContextFactory _snowflakeDbContextFactory; + private readonly ILogger _logger; + + public PostgresReplicationService(IIdServiceClient idServiceClient, + IReplicationServiceClient replicationService, + ISnowflakeDatabaseContextFactory snowflakeDbContextFactory, + ILogger logger) + { + _idServiceClient = idServiceClient; + _replicationService = replicationService; + _snowflakeDbContextFactory = snowflakeDbContextFactory; + _logger = logger; + } + + public async Task Replicate(List> entitiesByClient, string table, CancellationToken cancellationToken) where T1 : IPostgresReplicable + { + foreach (var entities in entitiesByClient) + { + if (!(await _replicationService.IsOnboarded(entities.Key, cancellationToken))) + { + continue; + } + + try + { + var fileName = $"{typeof(T1).Name}_{entities.Key}.csv"; + _logger.LogServiceInformation($"Replication of {entities.Count()} {typeof(T1).Name} for DB with Strata Id = {entities.Key} started"); + var databaseGuid = await _idServiceClient.GetDatabaseGUID(entities.Key); + var snowflakeDbContext = _snowflakeDbContextFactory.Create( + databaseGuid.ToString("N")); + + try + { + var tempFilePath = Path.Combine(Path.GetTempPath(), fileName); + + var startCsvTime = DateTime.Now; + var snowflakeEntities = entities.Select(ex => ex.MapToSnowflakeModel()); + await using (var writer = new StreamWriter(tempFilePath)) + await using (var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture))) + { + await csv.WriteRecordsAsync(snowflakeEntities); + } + var endCsvTime = DateTime.Now; + + await snowflakeDbContext.ExecuteCommand($"PUT file://{tempFilePath} {STAGE_NAME};"); + await snowflakeDbContext.ExecuteCommandAsync($"TRUNCATE TABLE {table};"); + await snowflakeDbContext.ExecuteCommandAsync($@" +COPY INTO {table} +FROM {STAGE_NAME}/{fileName} +FILE_FORMAT = (TYPE = CSV, PARSE_HEADER = TRUE) +MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE +ON_ERROR = 'CONTINUE';"); + var endUploadTime = DateTime.Now; + + var csvSeconds = endCsvTime.Subtract(startCsvTime).TotalSeconds; + var uploadSeconds = endUploadTime.Subtract(endCsvTime).TotalSeconds; + _logger.LogServiceInformation($"Replication of {typeof(T1).Name} completed successfully, CSV file generated in {csvSeconds} seconds " + + $"and uploaded into snowflake in {uploadSeconds} seconds"); + } + finally + { + await snowflakeDbContext.ExecuteCommandAsync($"REMOVE {STAGE_NAME}/{fileName};"); + } + } + catch (StrataHttpException ex) + { + _logger.LogServiceError(ex, $"Network issue happened or database with id = {entities.Key} not found. Skipping replication of {typeof(T1).Name} data."); + } + catch (Exception ex) + { + _logger.LogServiceError(ex, $"Unable to migrate {typeof(T1).Name} data to database with id = {entities.Key}"); + } + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Reports/IReportService.cs b/src/Strata.ContinuousImprovement.Biz/Reports/IReportService.cs new file mode 100644 index 0000000..9881fe5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Reports/IReportService.cs @@ -0,0 +1,22 @@ +using Strata.ContinuousImprovement.Biz.DbContexts.Reports; +using Strata.Stratasphere.Biz.DbContexts; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Reports +{ + public interface IReportService + { + Task> GetReportsByOpportunity(Guid opportunityGuid, Guid userGuid, bool isSdtEmployee, CancellationToken cancellationToken); + Task> GetReportsByModule(int sourceOfSavings, Guid userGuid, bool isSdtEmployee, CancellationToken cancellationToken); + Task> GetReports(Guid userGuid, bool isSdtEmployee, CancellationToken cancellationToken); + Task AddReportByOpportunity(Guid opportunityGuid, Guid reportGuid, CancellationToken cancellationToken); + Task AddReportByModule(int module, Guid reportGuid, CancellationToken cancellationToken); + Task DeleteReportByOpportunity(Guid opportunityGuid, Guid reportGuid, CancellationToken cancellationToken); + Task DeleteReportByModule(int module, Guid reportGuid, CancellationToken cancellationToken); + Task GetReportFolders(CancellationToken cancellationToken); + Task> GetFolderReports(Guid folderGuid, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Reports/ReportService.cs b/src/Strata.ContinuousImprovement.Biz/Reports/ReportService.cs new file mode 100644 index 0000000..99c7ef1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Reports/ReportService.cs @@ -0,0 +1,145 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Reports; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using Strata.Stratasphere.Biz.DbContexts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Reports +{ + public class ReportService : IReportService + { + protected readonly IAsyncDbContextFactory _dbContextFactory; + + public ReportService(IAsyncDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + public async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task> GetReportsByOpportunity(Guid opportunityGuid, Guid userGuid, bool isSdtEmployee, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var reports = await dbContext.OpportunityReports + .Where(r => r.OpportunityGUID == opportunityGuid) + .Join(dbContext.REReports, s => s.ReportGUID, r => r.ReportGUID, (s, r) => r).ToListAsync(cancellationToken); + reports.ForEach(r => r.IsOpportunityReport = true); + return await GetPermitted(reports, userGuid, isSdtEmployee, cancellationToken); + } + + public async Task> GetReportsByModule(int sourceOfSavings, Guid userGuid, bool isSdtEmployee, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var reports = await dbContext.SourceOfSavingsReports + .Where(r => r.SourceOfSavings == sourceOfSavings) + .Join(dbContext.REReports, s => s.ReportGUID, r => r.ReportGUID, (s, r) => r).ToListAsync(cancellationToken); + reports.ForEach(r => r.IsOpportunityReport = false); + return await GetPermitted(reports, userGuid, isSdtEmployee, cancellationToken); + } + + public async Task> GetReports(Guid userGuid, bool isSdtEmployee, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var reports = await dbContext.REReports.ToListAsync(cancellationToken); + return await GetPermitted(reports, userGuid, isSdtEmployee, cancellationToken); + } + + private async Task> GetPermitted(IEnumerable reports, Guid userGuid, bool isSdtEmployee, CancellationToken cancellationToken) + { + if (isSdtEmployee) { return reports.OrderBy(r => r.Name); } + var dbContext = await GetDbContext(cancellationToken); + return reports.Join(dbContext.S3ACLSimples + .Where(s => s.UserGuid == userGuid && s.IsReadable != null && s.IsReadable == true), + r => r.ReportGUID, s => s.LootId, (r, s) => r).OrderBy(r => r.Name); + } + + public async Task AddReportByOpportunity(Guid opportunityGuid, Guid reportGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var opportunityReport = await dbContext.OpportunityReports + .SingleOrDefaultAsync(r => r.OpportunityGUID == opportunityGuid && r.ReportGUID == reportGuid, cancellationToken); + if (opportunityReport != null) { return; } + opportunityReport = new OpportunityReport { OpportunityGUID = opportunityGuid, ReportGUID = reportGuid }; + await dbContext.OpportunityReports.AddAsync(opportunityReport, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task AddReportByModule(int module, Guid reportGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var sourceOfSavingsReport = await dbContext.SourceOfSavingsReports + .SingleOrDefaultAsync(r => r.SourceOfSavings == module && r.ReportGUID == reportGuid, cancellationToken); + if (sourceOfSavingsReport != null) { return; } + sourceOfSavingsReport = new SourceOfSavingsReport { SourceOfSavings = module, ReportGUID = reportGuid }; + await dbContext.SourceOfSavingsReports.AddAsync(sourceOfSavingsReport, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteReportByOpportunity(Guid opportunityGuid, Guid reportGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var opportunityReport = await dbContext.OpportunityReports + .SingleOrDefaultAsync(r => r.OpportunityGUID == opportunityGuid && r.ReportGUID == reportGuid, cancellationToken); + if (opportunityReport == null) { return; } + dbContext.Remove(opportunityReport); + await dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteReportByModule(int module, Guid reportGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var sourceOfSavingsReport = await dbContext.SourceOfSavingsReports + .SingleOrDefaultAsync(r => r.SourceOfSavings == module && r.ReportGUID == reportGuid, cancellationToken); + if (sourceOfSavingsReport == null) { return; } + dbContext.Remove(sourceOfSavingsReport); + await dbContext.SaveChangesAsync(cancellationToken); + } + + public async Task GetReportFolders(CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var folders = await dbContext.Folders + .Join(dbContext.REReports, + f => f.FolderGuid, + r => r.FolderGuid, + (f, r) => f).ToListAsync(cancellationToken); + var folderGuids = folders.Select(r => r.FolderGuid).Distinct(); + var parentGuids = folders.Where(pf => pf.ParentFolderGuid != Guid.Empty + && !folderGuids.Any(x => x == pf.ParentFolderGuid)).Select(pf => pf.ParentFolderGuid); + var parentFolders = await GetMissingParentFolders(parentGuids, cancellationToken); + var lookup = folders.Union(parentFolders).ToLookup(r => r.ParentFolderGuid); + foreach (var folder in lookup.SelectMany(x => x)) + { + folder.Folders = lookup[folder.FolderGuid].OrderBy(f => f.Name).ToList(); + folder.Reports = (await GetFolderReports(folder.FolderGuid, cancellationToken)).OrderBy(x => x.Name).ToList(); + } + var top = lookup[Guid.Empty].ToList(); + return new FolderTree(top.First()); + } + + private async Task> GetMissingParentFolders(IEnumerable folderGuids, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + var parentFolders = await dbContext.Folders + .Where(f => folderGuids.Contains(f.FolderGuid)).ToListAsync(cancellationToken); + if (!parentFolders.Any()) { return parentFolders; } + return parentFolders + .Union(await GetMissingParentFolders(parentFolders.Where(pf => pf.ParentFolderGuid != Guid.Empty).Select(pf => pf.ParentFolderGuid), cancellationToken)); + } + + public async Task> GetFolderReports(Guid folderGuid, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + return await dbContext.REReports.Where(r => r.FolderGuid == folderGuid).ToListAsync(cancellationToken); + } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Score/ScoreAttribute.cs b/src/Strata.ContinuousImprovement.Biz/Score/ScoreAttribute.cs new file mode 100644 index 0000000..a7c9241 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Score/ScoreAttribute.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Score +{ + [Table("viewScoreAttribute", Schema = "dbo")] + public class ViewScoreAttribute + { + public Guid DimensionGuid { get; set; } + public string FriendlyName { get; set; } + } + + [Table("ScoreAttribute", Schema = "dbo")] + public class ScoreAttribute + { + [Key] + public Guid AttributeGuid { get; set; } + public Guid DimensionGroupGuid { get; set; } + public string Name => FriendlyName; + public string SqlColumnName { get; set; } + public string FriendlyName { get; set; } + public bool IsKey { get; set; } + [NotMapped] + public string ColumnName { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Score/ScoreDimension.cs b/src/Strata.ContinuousImprovement.Biz/Score/ScoreDimension.cs new file mode 100644 index 0000000..9e5d861 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Score/ScoreDimension.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Score +{ + [Table("ScoreDimension", Schema = "dbo")] + public class ScoreDimension + { + public Guid DimensionGuid { get; set; } + public string FriendlyName { get; set; } + public Guid DefaultLookupKeyGuid { get; set; } + public Guid DefaultKeyGuid { get; set; } + public string GlobalId { get; set; } + public string SQLSchemaName { get; set; } + public string SQLObjectName { get; set; } + + [NotMapped] + public ScoreAttribute DefaultLookupKey { get; set; } + [NotMapped] + public ScoreAttribute DefaultKey { get; set; } + [NotMapped] + public string SQLObjectExpression + => $"{SQLSchemaName}.{SQLObjectName}"; + } + + public static class Dimension + { + public const string ServiceLine = "Service Line"; + public const string Sg2CareFamily = "Sg2 CARE Family"; + public const string Sg2ServiceLineCrosswalk = "Sg2 Service Line Crosswalk"; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Security/ISimpleSecurityService.cs b/src/Strata.ContinuousImprovement.Biz/Security/ISimpleSecurityService.cs new file mode 100644 index 0000000..2243900 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Security/ISimpleSecurityService.cs @@ -0,0 +1,13 @@ +using Strata.Authorization; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Security +{ + public interface ISimpleSecurityService + { + Task> GetAllIdentities(CancellationToken cancellationToken); + Task> GetSimplePermissionDefaults(SecurableEntityType entityType, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Security/Identity.cs b/src/Strata.ContinuousImprovement.Biz/Security/Identity.cs new file mode 100644 index 0000000..e6050c5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Security/Identity.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Security +{ + [Keyless] + public class Identity + { + public Guid IdentityGuid { get; set; } + public string IdentityName { get; set; } + } + + public class IdentityNode + { + public Guid Key { get; set; } + public string Title { get; set; } + public IdentityNode(Identity identity) + { + Key = identity.IdentityGuid; + Title = identity.IdentityName; + } + + public IdentityNode() + { + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Security/S3SimplePermission.cs b/src/Strata.ContinuousImprovement.Biz/Security/S3SimplePermission.cs new file mode 100644 index 0000000..bf88cc5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Security/S3SimplePermission.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Security +{ + [Table("S3SimplePermission", Schema = "dbo")] + public class S3SimplePermission + { + [Key] + public Guid PermissionGUID { get; set; } + public Guid LootID { get; set; } + public Guid IdentityGUID { get; set; } + public bool ReadValue { get; set; } + public bool WriteValue { get; set; } + public bool SecureValue { get; set; } + + public S3SimplePermission(Guid lootID, Guid identityGUID, bool readValue, bool writeValue, bool secureValue) + { + LootID = lootID; + IdentityGUID = identityGUID; + ReadValue = readValue; + WriteValue = writeValue; + SecureValue = secureValue; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Security/S3SimplePermissionDefault.cs b/src/Strata.ContinuousImprovement.Biz/Security/S3SimplePermissionDefault.cs new file mode 100644 index 0000000..a55d069 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Security/S3SimplePermissionDefault.cs @@ -0,0 +1,16 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Security +{ + [Table("S3SimplePermissionDefault", Schema = "dbo")] + public class S3SimplePermissionDefault + { + public int EntityType { get; set; } + public Guid IdentityGuid { get; set; } + public bool ReadValue { get; set; } + public bool WriteValue { get; set; } + public bool SecureValue { get; set; } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/Security/SimplePermission.cs b/src/Strata.ContinuousImprovement.Biz/Security/SimplePermission.cs new file mode 100644 index 0000000..7c6d75a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Security/SimplePermission.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Security +{ + //This class reprents a combination of user and user group permisions + [Keyless] + public class SimplePermission + { + public string IdentityName { get; set; } + public Guid LootID { get; set; } + public Guid IdentityGUID { get; set; } + public bool ReadValue { get; set; } + public bool WriteValue { get; set; } + public bool SecureValue { get; set; } + + public SimplePermission() + { + + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Security/SimpleSecurityService.cs b/src/Strata.ContinuousImprovement.Biz/Security/SimpleSecurityService.cs new file mode 100644 index 0000000..aec5db2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Security/SimpleSecurityService.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore; +using Strata.Authorization; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Security +{ + public class SimpleSecurityService : ISimpleSecurityService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + + public SimpleSecurityService(IAsyncDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + private async Task GetDbContext(CancellationToken cancellationToken) + { + return await _dbContextFactory.CreateDbContextAsync(cancellationToken); + } + + public async Task> GetAllIdentities(CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + return await dbContext.UserProfiles.Select(up => new Identity { IdentityGuid = up.UserGuid, IdentityName = up.NameFull }) + .Union(dbContext.UserGroups.Select(ug => new Identity { IdentityGuid = ug.UserGroupGuid, IdentityName = ug.Name })) + .Distinct().OrderBy(i => i.IdentityName) + .Select(i => new IdentityNode(i)) + .ToListAsync(cancellationToken); + } + + public async Task> GetSimplePermissionDefaults(SecurableEntityType entityType, CancellationToken cancellationToken) + { + var dbContext = await GetDbContext(cancellationToken); + + return await dbContext.S3SimplePermissionDefaults + .Join(dbContext.UserGroups, + d => d.IdentityGuid, + u => u.UserGroupGuid, + (d, u) => new { d.EntityType, u.UserGroupGuid, d.ReadValue, d.WriteValue, d.SecureValue, u.Name, u.GlobalId }) + .Where(dflt => dflt.EntityType == (int)entityType && (dflt.ReadValue || dflt.WriteValue)) + .Select(item => new SimplePermission() { IdentityGUID = item.UserGroupGuid, IdentityName = item.Name, ReadValue = item.ReadValue, WriteValue = item.WriteValue, SecureValue = item.SecureValue }) + .ToListAsync(cancellationToken); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/ServiceLines/ServiceLineInformation.cs b/src/Strata.ContinuousImprovement.Biz/ServiceLines/ServiceLineInformation.cs new file mode 100644 index 0000000..e4e9687 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/ServiceLines/ServiceLineInformation.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Score; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.ServiceLines +{ + public class ServiceLineInformation + { + private ScoreDimension ServiceLineDimension { get; set; } + private ScoreAttribute ServiceLineAttribute { get; set; } + private ScoreAttribute ServiceLineLookUpAttribute { get; set; } + + + public string FriendlyName => ServiceLineDimension.FriendlyName; + public string ServiceLineIdAttribute { get; set; } + public string ServiceLineTableName { get; set; } + public string ServiceLineNameAttribute { get; set; } + public string ServiceLineColumn { get; set; } = ""; + public string ServiceLineJoin { get; set; } = ""; + public string SelectClause { get; set; } = ""; + + public static async Task GetServiceLineInformationAsync(JazzDbContext dbContext, Configuration.Configuration config, string schema = "OBJ", CancellationToken cancellationToken = default) + { + var serviceLineInformation = new ServiceLineInformation(); + var serviceLineDimension = await config.GetServiceLineDimension(dbContext, cancellationToken); + var serviceLineAttribute = await config.GetServiceLineAttribute(dbContext, cancellationToken); + + serviceLineInformation.ServiceLineDimension = serviceLineDimension; + serviceLineInformation.ServiceLineAttribute = serviceLineAttribute; + + serviceLineInformation.ServiceLineLookUpAttribute = serviceLineDimension.DefaultLookupKey + ?? await dbContext.ScoreAttributes.FirstOrDefaultAsync(a => a.AttributeGuid == serviceLineDimension.DefaultLookupKeyGuid, cancellationToken); + + serviceLineInformation.ServiceLineIdAttribute = serviceLineAttribute.SqlColumnName; + serviceLineInformation.ServiceLineTableName = serviceLineInformation.ServiceLineDimension.SQLObjectExpression; + serviceLineInformation.ServiceLineNameAttribute = serviceLineInformation.ServiceLineLookUpAttribute?.ColumnName ?? serviceLineAttribute?.ColumnName ?? "Name"; + serviceLineInformation.SelectClause = $"\r\n, sl.{serviceLineInformation.ServiceLineNameAttribute} as ServiceLine"; + serviceLineInformation.ServiceLineColumn = $"sl.{serviceLineInformation.ServiceLineIdAttribute}"; + + if (serviceLineInformation.ServiceLineDimension.FriendlyName == Dimension.Sg2ServiceLineCrosswalk) + { + serviceLineInformation.ServiceLineJoin = $"\r\nINNER JOIN dss.DimSg2CareFamily cf ON cf.Sg2CAREFamilyID = {schema}.Sg2CAREFamilyID" + + "\r\nINNER JOIN dss.DimSg2ServiceLineCrosswalk sl ON sl.Sg2ServiceLine = cf.ServiceLine"; + } + else + { + serviceLineInformation.ServiceLineJoin = $"\r\nINNER JOIN {serviceLineInformation.ServiceLineTableName} sl on sl.{serviceLineInformation.ServiceLineIdAttribute} = {schema}." + + (schema == "m" ? "ServiceLineID" : serviceLineInformation.ServiceLineIdAttribute); + } + return serviceLineInformation; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Settings/ISettingsService.cs b/src/Strata.ContinuousImprovement.Biz/Settings/ISettingsService.cs new file mode 100644 index 0000000..620b4d0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Settings/ISettingsService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Settings +{ + public interface ISettingsService + { + Task> GetSystemSettings(List settings, CancellationToken cancellationToken); + Task> SystemSettingsAsync(CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Settings/SettingsService.cs b/src/Strata.ContinuousImprovement.Biz/Settings/SettingsService.cs new file mode 100644 index 0000000..7aa8cb8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Settings/SettingsService.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.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.ContinuousImprovement.Biz.Settings +{ + public class SettingsService : ISettingsService + { + private readonly IAsyncDbContextFactory _dbContextFactory; + private readonly ILogger _logger; + + public SettingsService() { } + + public SettingsService(IAsyncDbContextFactory dbContextFactory, ILogger logger) + { + _dbContextFactory = dbContextFactory; + _logger = logger; + } + + /// + /// Get necessary system settings + /// + /// List of system settings names + /// + /// Dictionary with system settings name-value + public async Task> GetSystemSettings(List settings, CancellationToken cancellationToken) + { + if (settings.IsNullOrEmpty()) { return new Dictionary(); } + var results = await SystemSettingsAsync(cancellationToken); + return results.Where(item => settings.Contains(item.Key)) + .ToDictionary(item => item.Key, item => item.Value); + } + + /// + /// Get dictionary of system settings + /// + /// + /// Dictionary with system settings + public async Task> SystemSettingsAsync(CancellationToken cancellationToken) + { + try + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) { return default; } + var results = await dbContext.SystemSettings.ToDictionaryAsync(item => item.Name, item => item.Value, cancellationToken); + return results; + } + catch (Exception ex) + { + _logger.LogServiceError(ex, ex.Message); + throw; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/DatabaseRegexSection.cs b/src/Strata.ContinuousImprovement.Biz/Shared/DatabaseRegexSection.cs new file mode 100644 index 0000000..d9fe619 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/DatabaseRegexSection.cs @@ -0,0 +1,11 @@ +using System.Text.RegularExpressions; + +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public class DatabaseRegexSection + { + public string DatabaseRegex { get; set; } + public Regex DatabaseRegexExpression => DatabaseRegex == null + ? null : new Regex(DatabaseRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/EncounterGroup.cs b/src/Strata.ContinuousImprovement.Biz/Shared/EncounterGroup.cs new file mode 100644 index 0000000..1745f20 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/EncounterGroup.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public enum EncounterGroup + { + Both = -1, + CaseType = 0, + PatientPopulation = 1 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/EnumExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Shared/EnumExtensions.cs new file mode 100644 index 0000000..c082a61 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/EnumExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel; +using System.Reflection; + +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public static class EnumExtensions + { + public static string GetDescription(this Enum value) + { + FieldInfo field = value.GetType().GetField(value.ToString()); + + if (field != null && + Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attr) + { + return attr.Description; + } + + return value.ToString(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/InitiativeStatuses.cs b/src/Strata.ContinuousImprovement.Biz/Shared/InitiativeStatuses.cs new file mode 100644 index 0000000..6ad56ab --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/InitiativeStatuses.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.Shared; + +public enum InitiativeStatuses +{ + OnHold, + InProcess, + Completed +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/MonthlyRampUp.cs b/src/Strata.ContinuousImprovement.Biz/Shared/MonthlyRampUp.cs new file mode 100644 index 0000000..4f2c6f8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/MonthlyRampUp.cs @@ -0,0 +1,39 @@ +using System.Globalization; + +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public sealed class MonthlyRampUp + { + #region Properties + + public int FiscalMonthId { get; private set; } + public int FiscalYearId { get; private set; } + public string Month { get; private set; } + public double Value { get; internal set; } + public double ValuePercentage { get { return Value / 100.0; } } + + #endregion + + #region Ctors + + public MonthlyRampUp(int fiscalMonthId, int fiscalYearId, double value) + { + FiscalMonthId = fiscalMonthId; + FiscalYearId = fiscalYearId; + Month = CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(fiscalMonthId); + Value = value; + } + + #endregion + + public static MonthlyRampUp[] GetDefaultRampUps() + { + return new[] + { + new MonthlyRampUp(1, 0, 1), new MonthlyRampUp(2, 0, 1), new MonthlyRampUp(3, 0, 1), new MonthlyRampUp(4, 0, 1), + new MonthlyRampUp(5, 0, 1), new MonthlyRampUp(6, 0, 1), new MonthlyRampUp(7, 0, 1), new MonthlyRampUp(8, 0, 1), + new MonthlyRampUp(9, 0, 1), new MonthlyRampUp(10, 0, 1), new MonthlyRampUp(11, 0, 1), new MonthlyRampUp(12, 0, 1) + }; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/NameItem.cs b/src/Strata.ContinuousImprovement.Biz/Shared/NameItem.cs new file mode 100644 index 0000000..d94b56e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/NameItem.cs @@ -0,0 +1,16 @@ +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public interface INameItem + { + string Name { get; set; } + } + + public class NameItem : INameItem + { + public string Name { get; set; } + public NameItem(string name) + { + Name = name; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/OpportunityLevel.cs b/src/Strata.ContinuousImprovement.Biz/Shared/OpportunityLevel.cs new file mode 100644 index 0000000..ab51f22 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/OpportunityLevel.cs @@ -0,0 +1,11 @@ +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public enum OpportunityLevel + { + Organization, + Entity, + ServiceLine, + Specialty, + Provider + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/Period.cs b/src/Strata.ContinuousImprovement.Biz/Shared/Period.cs new file mode 100644 index 0000000..dd04eb5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/Period.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public class Period + { + public T Start { get; private set; } + public T End { get; private set; } + + public Period(T start, T end) + { + Start = start; + End = end; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/PickerItem.cs b/src/Strata.ContinuousImprovement.Biz/Shared/PickerItem.cs new file mode 100644 index 0000000..7d37cf2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/PickerItem.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public class PickerItem + { + public long Value { get; set; } + public string Text { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/SystemSetting.cs b/src/Strata.ContinuousImprovement.Biz/Shared/SystemSetting.cs new file mode 100644 index 0000000..96e1321 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/SystemSetting.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.Shared +{ + [Table("SystemSetting", Schema = "cci")] + public class SystemSetting + { + [Key] + public int SystemSettingId { get; set; } + + public string Name { get; set; } + public string Description { get; set; } + public int ColumnType { get; set; } + public string Value { get; set; } + public string DefaultValue { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/TrackingYearMonth.cs b/src/Strata.ContinuousImprovement.Biz/Shared/TrackingYearMonth.cs new file mode 100644 index 0000000..42f78d5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/TrackingYearMonth.cs @@ -0,0 +1,22 @@ +using Strata.ContinuousImprovement.Biz.FiscalMonths; + +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public class TrackingYearMonth : YearMonth + { + public readonly bool IsAfterEffectiveDate; + public readonly bool IsBeforeTrackingDate; + public readonly bool IsCurrentMonth; + + public bool IsTrackedMonth => IsAfterEffectiveDate && IsBeforeTrackingDate; + + public TrackingYearMonth(int fiscalYear, int calendarYear, FiscalMonth fiscalMonth, bool isAfterEffectiveDate, + bool isBeforeTrackingDate, bool isCurrentMonth) + : base(fiscalYear, calendarYear, fiscalMonth) + { + IsAfterEffectiveDate = isAfterEffectiveDate; + IsBeforeTrackingDate = isBeforeTrackingDate; + IsCurrentMonth = isCurrentMonth; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Shared/YearMonth.cs b/src/Strata.ContinuousImprovement.Biz/Shared/YearMonth.cs new file mode 100644 index 0000000..d89287f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Shared/YearMonth.cs @@ -0,0 +1,39 @@ +using Strata.ContinuousImprovement.Biz.FiscalMonths; +using System; + +namespace Strata.ContinuousImprovement.Biz.Shared +{ + public class YearMonth + { + public readonly int FiscalYear; + public readonly int CalendarYear; + + public readonly FiscalMonth FiscalMonth; + + public readonly DateTime FiscalYearMonth; + + public readonly DateTime CalendarYearMonth; + + #region Helper Properties to corelate to database column names + + public int FiscalYearID => FiscalYear; + + /// + /// This is calendar month and FiscalMonth.SortOrder is the real FiscalMonth + /// we put it here so that we don't frikin confuse ourself between fiscalmonth and calendarmonth :x + /// + public int FiscalMonthID => FiscalMonth.FiscalMonthId; + + #endregion + + internal YearMonth(int fiscalYear, int calendarYear, FiscalMonth fiscalMonth) + { + FiscalYear = fiscalYear; + CalendarYear = calendarYear; + FiscalMonth = fiscalMonth; + + FiscalYearMonth = new DateTime(fiscalYear, fiscalMonth.SortOrder, 1); + CalendarYearMonth = new DateTime(calendarYear, fiscalMonth.FiscalMonthId, 1); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Strata.ContinuousImprovement.Biz.csproj b/src/Strata.ContinuousImprovement.Biz/Strata.ContinuousImprovement.Biz.csproj new file mode 100644 index 0000000..17b9757 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Strata.ContinuousImprovement.Biz.csproj @@ -0,0 +1,155 @@ + + + + net8.0 + + + + + 1701;1702 + + + + 1701;1702 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_Parameter1>Strata.ContinuousImprovement.Api.Test.Integration + + + <_Parameter1>Strata.ContinuousImprovement.Api.Test.Unit + + + <_Parameter1>Strata.ContinuousImprovement.Biz.Test.Unit + + + <_Parameter1>Strata.ContinuousImprovement.Biz.Test.Integration + + + <_Parameter1>Strata.ContinuousImprovement.Client.Test.Integration + + + <_Parameter1>Strata.ContinuousImprovement.JazzEntityFrameworkStub + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + Code + + + + + + + + + + + + + + diff --git a/src/Strata.ContinuousImprovement.Biz/Strata.ContinuousImprovement.Biz.csproj.DotSettings b/src/Strata.ContinuousImprovement.Biz/Strata.ContinuousImprovement.Biz.csproj.DotSettings new file mode 100644 index 0000000..8064d49 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Strata.ContinuousImprovement.Biz.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/DistributionChargeCodeSummary.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/DistributionChargeCodeSummary.cs new file mode 100644 index 0000000..d6ea504 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/DistributionChargeCodeSummary.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.ChargeCode +{ + public class DistributionChargeCodeSummary + { + public double ImprovementSum { get; set; } + public double EstimatedGoalSum { get; set; } + public double CommittedSum { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/DistributionProcessChargeCode.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/DistributionProcessChargeCode.cs new file mode 100644 index 0000000..bfcf18d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/DistributionProcessChargeCode.cs @@ -0,0 +1,61 @@ +using Dapper; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.Models; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.ChargeCode +{ + public class DistributionProcessChargeCode + { + const string REQUEST_TYPE = "Strategic Opportunity Detail"; + + private readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext; + private readonly DistributionProcessChargeCodeSqlBuilder _sqlBuilder; + + protected readonly long _opportunityId; + protected readonly Period _baselinePeriod; + protected readonly Period _goalPeriod; + protected readonly Period _trackingPeriod; + protected readonly double[] _rampUps; + + public DistributionProcessChargeCode(ISnowflakeDatabaseContext snowflakeDatabaseContext, long opportunityId, Period baselinePeriod, Period goalPeriod, Period trackingPeriod, double[] rampUps) + { + _snowflakeDatabaseContext = snowflakeDatabaseContext; + _opportunityId = opportunityId; + _baselinePeriod = baselinePeriod; + _goalPeriod = goalPeriod; + _trackingPeriod = trackingPeriod; + _rampUps = rampUps; + + _sqlBuilder = new DistributionProcessChargeCodeSqlBuilder(opportunityId, baselinePeriod, goalPeriod, trackingPeriod, rampUps); + } + + public async Task DistributeAsync(DistributionMethod methodType, DistributionStage stage) + { + var sqlBuilderResult = _sqlBuilder.BuildInsertDistributedSql(methodType, stage); + var result = await _snowflakeDatabaseContext.QueryMultipleReturnLastAsync(sqlBuilderResult.Sql, REQUEST_TYPE, sqlBuilderResult.QueryCount, sqlBuilderResult.Parameters); + return result.FirstOrDefault(); + } + + public async Task GetDistributionSummaryAsync(bool isInitiative) + { + var sqlBuilderResult = _sqlBuilder.BuildDistributionSummarySql(isInitiative); + var result = await _snowflakeDatabaseContext.QueryMultipleReturnLastAsync(sqlBuilderResult.Sql, REQUEST_TYPE, sqlBuilderResult.QueryCount, sqlBuilderResult.Parameters); + return result.FirstOrDefault(); + } + + public async Task UpdateDetailCommittedByRampUp(double[] oldRampUps) + { + var sqlBuilderResult = _sqlBuilder.BuildUpdateDetailCommittedByRampUpSql(oldRampUps); + var result = await _snowflakeDatabaseContext.QueryMultipleReturnLastAsync(sqlBuilderResult.Sql, REQUEST_TYPE, sqlBuilderResult.QueryCount, sqlBuilderResult.Parameters); + return result.FirstOrDefault(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/DistributionProcessChargeCodeSqlBuilder.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/DistributionProcessChargeCodeSqlBuilder.cs new file mode 100644 index 0000000..ee49c9e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/DistributionProcessChargeCodeSqlBuilder.cs @@ -0,0 +1,455 @@ +using Dapper; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.Models; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.ChargeCode +{ + public class DistributionProcessChargeCodeSqlBuilder + { + private readonly Assembly _assembly; + private readonly long _opportunityId; + private readonly Period _baselinePeriod; + private readonly Period _goalPeriod; + private readonly Period _trackingPeriod; + private readonly double[] _rampUps; + + public DistributionProcessChargeCodeSqlBuilder( + long opportunityId, + Period baselinePeriod, + Period goalPeriod, + Period trackingPeriod, + double[] rampUps) + { + _assembly = Assembly.GetExecutingAssembly(); + _opportunityId = opportunityId; + _baselinePeriod = baselinePeriod; + _goalPeriod = goalPeriod; + _trackingPeriod = trackingPeriod; + _rampUps = rampUps; + } + + public SqlBuilderResult BuildInsertDistributedSql(DistributionMethod methodType, DistributionStage stage) + { + var isInitiative = GetIsInitiative(stage); + var distributedPeriod = GetDistributedPeriod(isInitiative); + var sqlRampUpTable = GetRampUpTableSql(stage == DistributionStage.FromInitiativeToOpportunity ? _trackingPeriod : distributedPeriod, _rampUps, "rp"); + + var sbQuery = new StringBuilder(); + var queryCount = (byte)0; + + if (stage == DistributionStage.FromOpportunityToInitiative || stage == DistributionStage.FromInitiativeToOpportunity || stage == DistributionStage.InitiativeChangeTrackingPeriod) + { + if (!string.IsNullOrEmpty(sqlRampUpTable)) + { + Func convertFunc = stage == DistributionStage.FromOpportunityToInitiative ? + GetFromGoalToCommittedSql : + GetFromCommittedToGoalSql; + sbQuery.AppendLine($@" + UPDATE {StrategicOpportunityService.StrategicOpportunityDetailTableName} AS tt + SET + tt.GoalUnits = st.GoalUnits, + tt.GoalCost = st.GoalCost + FROM ( + SELECT + RowId, + (CASE WHEN rp.RampUp IS NULL OR rp.RampUp = 1 THEN GoalUnits ELSE {convertFunc("GoalUnits", "DistributedUnits", "rp.RampUp")} END) AS GoalUnits, + (CASE WHEN rp.RampUp IS NULL OR rp.RampUp = 1 THEN GoalCost ELSE {convertFunc("GoalCost", "DistributedCost", "rp.RampUp")} END) AS GoalCost + + FROM {StrategicOpportunityService.StrategicOpportunityDetailTableName} AS sd + LEFT JOIN {sqlRampUpTable} ON rp.YearId = sd.DischargeFiscalYearId AND rp.MonthId = sd.DischargeFiscalMonthId + WHERE OpportunityId = @OpportunityId AND IsBaseline = False + ) AS st + WHERE st.RowId = tt.RowId; + "); + queryCount += 1; + } + } + + if (stage == DistributionStage.FromInitiativeToOpportunity) + { + sbQuery.AppendLine($@" + UPDATE {StrategicOpportunityService.StrategicOpportunityDetailTableName} AS tt + SET + tt.Cost = 0, + tt.Units = 0 + WHERE tt.OpportunityId = @OpportunityId AND tt.IsBaseline = False; + "); + queryCount += 1; + } + + if (!((stage == DistributionStage.FromOpportunityToInitiative || stage == DistributionStage.FromInitiativeToOpportunity) && IsDistributedPeriodsEqual())) + { + if (IsUseMultiplier(stage)) + { + sbQuery.AppendLine(ReadResource("DetailSourceDataMultiplier.sql")); + queryCount += 3; + } + sbQuery.AppendLine($"DELETE FROM {StrategicOpportunityService.StrategicOpportunityDetailTableName} WHERE OpportunityId = @OpportunityId AND IsBaseline = False;"); + sbQuery.AppendLine("DECLARE"); + sbQuery.AppendLine(ReadResource("DeclareVariables.sql")); + sbQuery.AppendLine("BEGIN"); + sbQuery.AppendLine($@" + {GetInsertSql()} + WITH + {ReadResource("DatePeriodCTE.sql")}, + {ReadResource(GetDetailDataCteFile(methodType))} + {GetSelectSql(stage)}; + "); + sbQuery.AppendLine("END;"); + queryCount += 2; + } + + sbQuery.AppendLine(GetSummarySql(isInitiative)); + queryCount += 1; + + var parameters = GetParameters(sbQuery, queryCount, distributedPeriod); + + return new SqlBuilderResult() + { + Sql = sbQuery.ToString().SnowflakeSyntax(), + QueryCount = queryCount, + Parameters = parameters + }; + } + + public SqlBuilderResult BuildDistributionSummarySql(bool isInitiative) + { + var sbQuery = new StringBuilder(GetSummarySql(isInitiative)); + var queryCount = (byte)1; + + var parameters = GetParameters(sbQuery, queryCount, GetDistributedPeriod(isInitiative)); + + return new SqlBuilderResult() + { + Sql = sbQuery.ToString().SnowflakeSyntax(), + QueryCount = queryCount, + Parameters = parameters + }; + } + + public SqlBuilderResult BuildUpdateDetailCommittedByRampUpSql(double[] oldRampUps) + { + var isInitiative = true; + var queryCount = (byte)0; + var sbQuery = new StringBuilder(); + + var distributedPeriod = GetDistributedPeriod(isInitiative); + + var sqlOldRampUpTable = GetRampUpTableSql(distributedPeriod, oldRampUps, "orp"); + var sqlNewRampUpTable = GetRampUpTableSql(distributedPeriod, _rampUps, "nrp"); + + if (!(string.IsNullOrEmpty(sqlOldRampUpTable) && string.IsNullOrEmpty(sqlNewRampUpTable))) + { + if (string.IsNullOrEmpty(sqlOldRampUpTable)) sqlOldRampUpTable = GetRampUpTableSql(distributedPeriod, oldRampUps, "orp", true); + if (string.IsNullOrEmpty(sqlNewRampUpTable)) sqlNewRampUpTable = GetRampUpTableSql(distributedPeriod, _rampUps, "nrp", true); + + sbQuery.AppendLine($@" + UPDATE {StrategicOpportunityService.StrategicOpportunityDetailTableName} AS tt + SET + tt.GoalUnits = st.GoalUnits, + tt.GoalCost = st.GoalCost + FROM ( + SELECT + RowId, + ({GetFromCommittedToGoalOnRampUpUpdateSql("GoalUnits", "DistributedUnits", "orp.RampUp", "nrp.RampUp")}) AS GoalUnits, + ({GetFromCommittedToGoalOnRampUpUpdateSql("GoalCost", "DistributedCost", "orp.RampUp", "nrp.RampUp")}) AS GoalCost + FROM {StrategicOpportunityService.StrategicOpportunityDetailTableName} AS sd + LEFT JOIN {sqlOldRampUpTable} ON orp.YearId = sd.DischargeFiscalYearId AND orp.MonthId = sd.DischargeFiscalMonthId + LEFT JOIN {sqlNewRampUpTable} ON nrp.YearId = sd.DischargeFiscalYearId AND nrp.MonthId = sd.DischargeFiscalMonthId + WHERE OpportunityId = @OpportunityId AND IsBaseline = False + ) AS st + WHERE st.RowId = tt.RowId; + "); + queryCount += 1; + } + + sbQuery.AppendLine(GetSummarySql(isInitiative)); + queryCount += 1; + + var parameters = GetParameters(sbQuery, queryCount, GetDistributedPeriod(isInitiative)); + + return new SqlBuilderResult() + { + Sql = sbQuery.ToString().SnowflakeSyntax(), + QueryCount = queryCount, + Parameters = parameters + }; + } + + private string ReadResource(string fileName) + { + return _assembly.ReadResource($"ChargeCode.SQL.{fileName}"); + } + + private bool GetIsInitiative(DistributionStage stage) + { + return stage switch + { + DistributionStage.FromSourceToOpportunity => false, + DistributionStage.OpportunityChangeGoalPeriod => false, + DistributionStage.FromOpportunityToInitiative => true, + DistributionStage.InitiativeChangeTrackingPeriod => true, + DistributionStage.FromInitiativeToOpportunity => false, + _ => false + }; + } + + private Period GetDistributedPeriod(bool isInitiative) + { + return isInitiative ? _trackingPeriod : _goalPeriod; + } + + private bool IsDistributedPeriodsEqual() + { + return _goalPeriod.Start.Year == _trackingPeriod.Start.Year && + _goalPeriod.Start.Month == _trackingPeriod.Start.Month && + _goalPeriod.End.Year == _trackingPeriod.End.Year && + _goalPeriod.End.Month == _trackingPeriod.End.Month; + } + + private bool IsUseMultiplier(DistributionStage stage) + { + return stage == DistributionStage.FromOpportunityToInitiative || stage == DistributionStage.FromInitiativeToOpportunity || stage == DistributionStage.InitiativeChangeTrackingPeriod || stage == DistributionStage.OpportunityChangeGoalPeriod; + } + + private string GetSelectSql(DistributionStage stage) + { + var distributionPeriod = GetDistributedPeriod(GetIsInitiative(stage)); + var sqlRampUpTable = GetRampUpTableSql(distributionPeriod, _rampUps, "rp", true); + var isUseMultiplier = IsUseMultiplier(stage); + + string goalValuesSql = $@" + dd.Units AS GoalUnits, + dd.Cost AS GoalCost + "; + + if (!string.IsNullOrEmpty(sqlRampUpTable) && isUseMultiplier) + { + if (stage == DistributionStage.FromInitiativeToOpportunity) + { + goalValuesSql = $@" + dd.Units * MultiplierUnits AS GoalUnits, + dd.Cost * MultiplierCost AS GoalCost + "; + } + else + { + goalValuesSql = $@" + (CASE WHEN mlt.MultiplierUnits IS NULL THEN dd.Units ELSE dd.Units * mlt.MultiplierUnits END) AS NewGoalUnits, + (CASE WHEN mlt.MultiplierCost IS NULL THEN dd.Cost ELSE dd.Cost * mlt.MultiplierCost END) AS NewGoalCost, + (CASE WHEN rp.RampUp IS NULL OR rp.RampUp = 1 THEN NewGoalUnits ELSE {GetFromGoalToCommittedSql("NewGoalUnits", "dd.Units", "rp.RampUp")} END) AS GoalUnits, + (CASE WHEN rp.RampUp IS NULL OR rp.RampUp = 1 THEN NewGoalCost ELSE {GetFromGoalToCommittedSql("NewGoalCost", "dd.Cost", "rp.RampUp")} END) AS GoalCost + "; + } + } + + string joinSql = !string.IsNullOrEmpty(sqlRampUpTable) && isUseMultiplier ? $@" + LEFT JOIN DetailSourceDataMultiplier AS mlt ON + mlt.EntityID = dd.EntityID AND + mlt.PrimaryPhysicianID = dd.PrimaryPhysicianID AND + mlt.PrimaryServiceLineID = dd.PrimaryServiceLineID AND + mlt.DepartmentID = dd.DepartmentID AND + mlt.PhysicianSpecialtyID = dd.PhysicianSpecialtyID AND + mlt.ChargeCodeID = dd.ChargeCodeID AND + mlt.CaseTypeFamilyId = dd.CaseTypeFamilyId AND + mlt.CostDriver = dd.CostDriver + LEFT JOIN {sqlRampUpTable} ON rp.YearId = dd.YearId AND rp.MonthId = dd.MonthId + " : string.Empty; + + var sql = $@" + SELECT + {GetColumnsList()} + FROM ( + SELECT + @OpportunityId AS OpportunityId, + False AS IsBaseline, + dd.YearId AS DischargeFiscalYearId, + dd.MonthId AS DischargeFiscalMonthId, + dd.EntityID, + dd.PrimaryPhysicianId, + dd.PrimaryServiceLineId, + dd.DepartmentId, + dd.PhysicianSpecialtyId, + dd.ChargeCodeID, + dd.CaseTypeFamilyId, + dd.CostDriver, + 0 AS Units, + 0 AS Cost, + dd.Units AS DistributedUnits, + dd.Cost AS DistributedCost, + {goalValuesSql} + FROM DetailNewAllData AS dd + {joinSql} + ) + "; + return sql; + } + + private string GetInsertSql() + { + var sql = $@" + INSERT INTO {StrategicOpportunityService.StrategicOpportunityDetailTableName} ( + {GetColumnsList()} + ) + "; + return sql; + } + + private string GetColumnsList() + { + return @" + OpportunityId, + IsBaseline, + DischargeFiscalYearId, + DischargeFiscalMonthId, + EntityId, + PrimaryPhysicianId, + PrimaryServiceLineId, + DepartmentId, + PhysicianSpecialtyId, + ChargeCodeId, + CaseTypeFamilyId, + CostDriver, + Units, + Cost, + DistributedUnits, + DistributedCost, + GoalUnits, + GoalCost"; + } + + private string GetSummarySql(bool isInitiative) + { + if (isInitiative) + { + var sql = $@" + SELECT + 0::FLOAT AS ImprovementSum, + 0::FLOAT AS EstimatedGoalSum, + COALESCE(SUM(GoalCost), 0)::FLOAT AS CommittedSum + FROM {StrategicOpportunityService.StrategicOpportunityDetailTableName} AS dl + WHERE OpportunityId = @OpportunityId AND IsBaseline = False; + "; + return sql; + } + else + { + var sqlRampUpTable = GetRampUpTableSql(GetDistributedPeriod(isInitiative), _rampUps, "rp"); + + var estimatedGoalSumSql = !string.IsNullOrEmpty(sqlRampUpTable) ? + $"COALESCE(SUM((CASE WHEN rp.RampUp IS NULL OR rp.RampUp = 1 THEN GoalCost ELSE {GetFromGoalToCommittedSql("GoalCost", "DistributedCost", " rp.RampUp")} END)), 0)::FLOAT AS EstimatedGoalSum" : + "COALESCE(SUM(GoalCost), 0)::FLOAT AS EstimatedGoalSum"; + var joinSql = !string.IsNullOrEmpty(sqlRampUpTable) ? + $"LEFT JOIN {sqlRampUpTable} ON rp.YearId = sd.DischargeFiscalYearId AND rp.MonthId = sd.DischargeFiscalMonthId" : + string.Empty; + + var sql = $@" + SELECT + COALESCE(SUM(DistributedCost - GoalCost), 0)::FLOAT AS ImprovementSum, + {estimatedGoalSumSql}, + 0::FLOAT AS CommittedSum + FROM {StrategicOpportunityService.StrategicOpportunityDetailTableName} AS sd + {joinSql} + WHERE OpportunityId = @OpportunityId AND IsBaseline = False; + "; + return sql; + } + } + + private string GetDetailDataCteFile(DistributionMethod methodType) + { + if (methodType == DistributionMethod.Monthly) + { + return "DetailDataMonthlyCTE.sql"; + } + else + { + return "DetailDataAverageCTE.sql"; + } + } + + private string GetRampUpTableSql(Period period, double[] rampUps, string tableName = "RampUpTable", bool isReturnFakeOnEmpty = false) + { + if (rampUps.Length == 0) + { + if (isReturnFakeOnEmpty) + { + return $@" + (SELECT + {period.Start.Year} AS YearId, + {period.Start.Month} AS MonthId, + 1.0 AS RampUp + ) AS {tableName} (YearId, MonthId, RampUp) + "; + } + return string.Empty; + } + var baselineYearMonthList = YearMonthStruct.GetList(new YearMonthStruct(period.Start), new YearMonthStruct(period.End)); + var sql = $@" + (VALUES + {string.Join($",{Environment.NewLine}", baselineYearMonthList.Take(rampUps.Length).Select((x, ind) => $"({x.Year}, {x.Month}, {rampUps[ind]})"))} + ) AS {tableName} (YearId, MonthId, RampUp) + "; + return sql; + } + + private DynamicParameters GetParameters(StringBuilder sbQuery, byte queryCount, Period distributionPeriod) + { + var parameters = new Dictionary + { + { "@OpportunityId", _opportunityId.ToString() }, + { "@BaselineDateStart", $"'{_baselinePeriod.Start.ToString("yyyy-MM-dd")}'" }, + { "@BaselineDateEnd", $"'{_baselinePeriod.End.ToString("yyyy-MM-dd")}'" }, + { "@GoalDateStart", $"'{distributionPeriod.Start.ToString("yyyy-MM-dd")}'" }, + { "@GoalDateEnd", $"'{distributionPeriod.End.ToString("yyyy-MM-dd")}'" } + }; + + foreach (var x in parameters) + { + sbQuery = sbQuery.Replace(x.Key, x.Value); + } + + var queryOneParams = new DynamicParameters(); + queryOneParams.Add("MULTI_STATEMENT_COUNT", queryCount); + + return queryOneParams; + } + + private string GetFromGoalToCommittedSql(string goalColumn, string distributedColumn, string rampUpColumn) + { + var sql = $"({distributedColumn} - ({distributedColumn} - {goalColumn}) * {rampUpColumn})"; + return sql; + } + + private string GetFromCommittedToGoalSql(string goalColumn, string distributedColumn, string rampUpColumn) + { + var sql = $"(({goalColumn} - {distributedColumn} * (1 - {rampUpColumn})) / {rampUpColumn})"; + return sql; + } + + private string GetFromCommittedToCommittedSql(string goalColumn, string distributedColumn, string oldRampUpColumn, string newRampUpColumn) + { + var sql = GetFromGoalToCommittedSql(GetFromCommittedToGoalSql(goalColumn, distributedColumn, oldRampUpColumn), distributedColumn, newRampUpColumn); + return sql; + } + + private string GetFromCommittedToGoalOnRampUpUpdateSql(string goalColumn, string distributedColumn, string rampUpOldColumn, string rampUpNewColumn) + { + var sql = @$"CASE + WHEN ({rampUpOldColumn} IS NULL OR {rampUpOldColumn} = 1 ) AND ({rampUpNewColumn} IS NULL OR {rampUpNewColumn} = 1) THEN {goalColumn} + WHEN {rampUpOldColumn} <> 1 AND ({rampUpNewColumn} IS NULL OR {rampUpNewColumn} = 1) THEN {GetFromCommittedToGoalSql(goalColumn, distributedColumn, rampUpOldColumn)} + WHEN ({rampUpOldColumn} IS NULL OR {rampUpOldColumn} = 1 ) AND {rampUpNewColumn} <> 1 THEN {GetFromGoalToCommittedSql(goalColumn, distributedColumn, rampUpNewColumn)} + WHEN {rampUpOldColumn} <> 1 AND {rampUpNewColumn} <> 1 THEN {GetFromCommittedToCommittedSql(goalColumn, distributedColumn, rampUpOldColumn, rampUpNewColumn)} + ELSE {goalColumn} + END"; + return sql; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DatePeriodCTE.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DatePeriodCTE.sql new file mode 100644 index 0000000..ee1ddc8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DatePeriodCTE.sql @@ -0,0 +1,36 @@ +BasePeriod AS ( + SELECT + MONTH(monthDate) AS MonthId, + YEAR(monthDate) AS YearId + FROM ( + SELECT + DATEADD('month', seq4(), @BaselineDateStart) AS monthDate + FROM TABLE(GENERATOR(ROWCOUNT => @BaselineMonthCount)) + ) +), +GoalPeriod AS ( + SELECT + MONTH(monthDate) AS MonthId, + YEAR(monthDate) AS YearId + FROM ( + SELECT + DATEADD('month', seq4(), @GoalDateStart) AS monthDate + FROM TABLE(GENERATOR(ROWCOUNT => @GoalMonthCount)) + ) +), +MonthData AS ( + SELECT + COALESCE(base.MonthId, goal.MonthId) AS MonthId, + COALESCE(base.monthCount, 0) AS BaselineMonthCount, + COALESCE(goal.monthCount, 0) AS GoalMonthCount, + (case when GoalMonthCount > 0 and BaselineMonthCount > 0 then True else False end)::BOOLEAN AS IsCross + FROM (SELECT MonthId, COUNT(*) AS monthCount FROM GoalPeriod group by MonthId) AS goal + FULL JOIN (SELECT MonthId, COUNT(*) AS monthCount FROM BasePeriod group by MonthId) AS base + ON base.MonthId = goal.MonthId +), +MonthDataSummary AS ( + SELECT + SUM(case when IsCross = True then GoalMonthCount else 0 end) AS CrossCount, + SUM(case when IsCross = True then 0 else GoalMonthCount end) AS NotCrossCount, + FROM MonthData +) \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DeclareVariables.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DeclareVariables.sql new file mode 100644 index 0000000..d42b581 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DeclareVariables.sql @@ -0,0 +1,2 @@ +GoalMonthCount INTEGER := (SELECT DATEDIFF('month', @GoalDateStart, @GoalDateEnd) + 1); +BaselineMonthCount INTEGER := (SELECT DATEDIFF('month', @BaselineDateStart, @BaselineDateEnd) + 1); \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DetailDataAverageCTE.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DetailDataAverageCTE.sql new file mode 100644 index 0000000..93f7332 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DetailDataAverageCTE.sql @@ -0,0 +1,42 @@ +DetailData AS ( + SELECT + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CaseTypeFamilyId, + CostDriver, + (CASE WHEN @BaselineMonthCount = 0 THEN 0 ELSE (SUM(Units)/@BaselineMonthCount) END) AS Units, + (CASE WHEN @BaselineMonthCount = 0 THEN 0 ELSE (SUM(Cost)/@BaselineMonthCount) END) AS Cost + FROM cci.FactStrategicChargeCodeDetail + WHERE IsBaseline = True AND OpportunityId = @OpportunityId + GROUP BY + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CaseTypeFamilyId, + CostDriver +), + +DetailNewAllData AS ( + SELECT + gp.YearId, + gp.MonthId, + dd.EntityID, + dd.PrimaryPhysicianID, + dd.PrimaryServiceLineID, + dd.DepartmentID, + dd.PhysicianSpecialtyID, + dd.ChargeCodeID, + dd.CaseTypeFamilyId, + dd.CostDriver, + dd.Units, + dd.Cost + FROM DetailData AS dd + INNER JOIN GoalPeriod AS gp +) \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DetailDataMonthlyCTE.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DetailDataMonthlyCTE.sql new file mode 100644 index 0000000..6f6ac8f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DetailDataMonthlyCTE.sql @@ -0,0 +1,148 @@ +DetailMonthData AS ( + SELECT + DischargeFiscalMonthId AS MonthId, + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CaseTypeFamilyId, + CostDriver, + SUM(Units) AS SumMonthUnits, + SUM(Cost) AS SumMonthCost, + SUM(md.BaselineMonthCount) AS SumBaselineMonthCount, + SUM(md.GoalMonthCount) AS SumGoalMonthCount, + (CASE WHEN SumBaselineMonthCount = 0 OR SumGoalMonthCount = 0 THEN 0 ELSE ((SumMonthUnits/SumBaselineMonthCount) * SumGoalMonthCount) END) AS SumGoalMonthUnits, + (CASE WHEN SumBaselineMonthCount = 0 OR SumGoalMonthCount = 0 THEN 0 ELSE ((SumMonthCost/SumBaselineMonthCount) * SumGoalMonthCount) END) AS SumGoalMonthCost, + md.IsCross + FROM cci.FactStrategicChargeCodeDetail AS sd + INNER JOIN MonthData AS md ON md.MonthId = sd.DischargeFiscalMonthId + WHERE IsBaseline = True AND OpportunityId = @OpportunityId + GROUP BY + IsCross, + DischargeFiscalMonthId, + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CaseTypeFamilyId, + CostDriver +), + +DetailTotalData AS ( + SELECT + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CaseTypeFamilyId, + CostDriver, + SUM(dmd.SumMonthUnits) AS SumUnits, + SUM(dmd.SumMonthCost) AS SumCost, + (CASE WHEN @BaselineMonthCount = 0 THEN 0 ELSE (SumUnits/@BaselineMonthCount) END) AS AverageUnits, + (CASE WHEN @BaselineMonthCount = 0 THEN 0 ELSE (SumCost/@BaselineMonthCount) END) AS AverageCost, + SUM(case when IsCross = True then dmd.SumGoalMonthUnits else 0 end) AS CrossSumGoalMonthUnits, + SUM(case when IsCross = True then dmd.SumGoalMonthCost else 0 end) AS CrossSumGoalMonthCost, + SUM(case when IsCross = True then 1 else 0 end) AS CrossMonthCount + FROM DetailMonthData AS dmd + GROUP BY + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CaseTypeFamilyId, + CostDriver +), + +DetailNewCross AS ( + SELECT + md.MonthId AS MonthId, + dmd.EntityID, + dmd.PrimaryPhysicianID, + dmd.PrimaryServiceLineID, + dmd.DepartmentID, + dmd.PhysicianSpecialtyID, + dmd.ChargeCodeID, + dmd.CaseTypeFamilyId, + dmd.CostDriver, + (case when dtd.CrossSumGoalMonthUnits = 0 then 0 else ((dtd.AverageUnits * mds.CrossCount) * (dmd.SumGoalMonthUnits / dtd.CrossSumGoalMonthUnits)/md.GoalMonthCount) end) AS Units, + (case when dtd.CrossSumGoalMonthCost = 0 then 0 else ((dtd.AverageCost * mds.CrossCount) * (dmd.SumGoalMonthCost / dtd.CrossSumGoalMonthCost)/md.GoalMonthCount) end) AS Cost + FROM MonthData AS md + INNER JOIN MonthDataSummary AS mds + INNER JOIN DetailMonthData AS dmd on + dmd.MonthId = md.MonthId + AND md.IsCross = True + INNER JOIN DetailTotalData AS dtd ON + dtd.EntityID = dmd.EntityID AND + dtd.PrimaryPhysicianID = dmd.PrimaryPhysicianID AND + dtd.PrimaryServiceLineID = dmd.PrimaryServiceLineID AND + dtd.DepartmentID = dmd.DepartmentID AND + dtd.PhysicianSpecialtyID = dmd.PhysicianSpecialtyID AND + dtd.ChargeCodeID = dmd.ChargeCodeID AND + dtd.CaseTypeFamilyId = dmd.CaseTypeFamilyId AND + dtd.CostDriver = dmd.CostDriver +), + +DetailNewNotCross AS ( + SELECT + md.MonthId AS MonthId, + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CaseTypeFamilyId, + CostDriver, + (case when md.BaselineMonthCount = 0 then AverageUnits else 0.0 end) AS Units, + (case when md.BaselineMonthCount = 0 then AverageCost else 0.0 end) AS Cost + FROM MonthData AS md + CROSS JOIN DetailTotalData AS dtd + WHERE md.GoalMonthCount > 0 + AND (md.BaselineMonthCount = 0 OR dtd.CrossMonthCount = 0) +), + +DetailData AS ( + SELECT + dtd.MonthId, + dtd.EntityID, + dtd.PrimaryPhysicianID, + dtd.PrimaryServiceLineID, + dtd.DepartmentID, + dtd.PhysicianSpecialtyID, + dtd.ChargeCodeID, + dtd.CaseTypeFamilyId, + dtd.CostDriver, + dtd.Units, + dtd.Cost + FROM ( + SELECT * FROM DetailNewCross + UNION + SELECT * FROM DetailNewNotCross + ) AS dtd +), + +DetailNewAllData AS ( + SELECT + gp.YearId, + gp.MonthId, + dd.EntityID, + dd.PrimaryPhysicianID, + dd.PrimaryServiceLineID, + dd.DepartmentID, + dd.PhysicianSpecialtyID, + dd.ChargeCodeID, + dd.CaseTypeFamilyId, + dd.CostDriver, + dd.Units, + dd.Cost + FROM DetailData AS dd + INNER JOIN GoalPeriod AS gp ON gp.MonthId = dd.MonthId +) \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DetailSourceDataMultiplier.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DetailSourceDataMultiplier.sql new file mode 100644 index 0000000..cc88c12 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/ChargeCode/SQL/DetailSourceDataMultiplier.sql @@ -0,0 +1,56 @@ +DROP TABLE IF EXISTS DetailSourceDataMultiplier; +CREATE TEMPORARY TABLE DetailSourceDataMultiplier ( + EntityId INT NOT NULL, + PrimaryPhysicianId INT NOT NULL, + PrimaryServiceLineId INT NOT NULL, + DepartmentId INT NOT NULL, + PhysicianSpecialtyId INT NOT NULL, + ChargeCodeId INT NOT NULL, + CaseTypeFamilyId INT NOT NULL, + CostDriver VARCHAR(1024) NOT NULL COLLATE 'en-ci', + MultiplierUnits FLOAT NOT NULL, + MultiplierCost FLOAT NOT NULL +); + +INSERT INTO DetailSourceDataMultiplier +WITH DetailSourceDataMultiplierCTE AS ( + SELECT + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CaseTypeFamilyId, + CostDriver, + SUM(DistributedUnits) AS UnitsDistributed, + SUM(GoalUnits) AS UnitsGoal, + SUM(DistributedCost) AS CostDistributed, + SUM(GoalCost) AS CostGoal, + (CASE WHEN UnitsGoal = 0 OR UnitsDistributed = 0 THEN 1 ELSE UnitsGoal / UnitsDistributed END) AS MultiplierUnits, + (CASE WHEN CostGoal = 0 OR CostDistributed = 0 THEN 1 ELSE CostGoal / CostDistributed END) AS MultiplierCost + FROM cci.FactStrategicChargeCodeDetail + WHERE OpportunityId = @OpportunityId AND IsBaseline = False + GROUP BY + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CostDriver, + CaseTypeFamilyId +) + +SELECT + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CaseTypeFamilyId, + CostDriver, + MultiplierUnits, + MultiplierCost +FROM DetailSourceDataMultiplierCTE; diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/DISTRIBUTION_PROCESS.md b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/DISTRIBUTION_PROCESS.md new file mode 100644 index 0000000..a2cb671 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/DISTRIBUTION_PROCESS.md @@ -0,0 +1,130 @@ +# Distribution Process + +## What It Does + +The distribution process takes baseline period cost/unit data from Snowflake and distributes it +across the **goal period** (or **tracking period**), applying ramp-up percentages and distribution +method rules. The output is written back to the fact table as `IsBaseline = FALSE` rows. + +This is how "what do we expect to save each month?" rows are generated. + +--- + +## Two Parallel Implementations + +| Class | Type | Fact Table | +|---|---|---| +| `DistributionProcessChargeCode` | Charge code opportunities | `cci.FactStrategicChargeCodeDetail` | +| `DistributionProcessGL` | GL opportunities | `cci.FactStrategicGLDetail` | + +Both are created by `DistributionProcessFactory` using `ActivatorUtilities.CreateInstance` — this +allows the factory to inject `ISnowflakeDatabaseContext` while the caller controls the other +constructor arguments (periods, ramp-ups). + +--- + +## Constructor Arguments + +Both process classes take the same set of arguments: + +```csharp +(long opportunityId, + Period baselinePeriod, + Period goalPeriod, + Period trackingPeriod, + double[] rampUps) +``` + +`rampUps` is an ordered array where index 0 = month 1 of the distributed period, index N-1 = last +month. Values are decimal fractions (e.g. `0.5` = 50%). + +--- + +## DistributionStage Enum + +`DistributionStage` controls which SQL path executes inside `BuildInsertDistributedSql`: + +| Stage | Meaning | When called | +|---|---|---| +| `Initial` | First population of goal rows | After creating an opportunity | +| `FromOpportunityToInitiative` | Convert opportunity → initiative | `SetStatus` transition to `NewInitiative` | +| `FromInitiativeToOpportunity` | Revert initiative → opportunity | `SetStatus` back below `NewInitiative` | +| `InitiativeChangeTrackingPeriod` | Tracking dates changed on existing initiative | `UpdateInitiative` with new tracking dates | +| `RampUpChange` | Ramp-up percentages were edited | `UpdateRampUps` | + +--- + +## Two-Phase Population Strategy + +### Phase 1 — Goal Period (Opportunity) + +Called by `PopulateStrategicOpportunityGoalDetails` when the opportunity is in `IsOpportunity = true` +state. Distributes baseline data across the **goal period** (computed: `BaselineStart+1mo` through +`BaselineStart+EstimatedDuration mo`). Goal period is fixed until the opportunity is converted. + +### Phase 2 — Committed Period (Initiative) + +Called by `PopulateStrategicOpportunityCommittedDetails` when converting to initiative or when +tracking dates change. Distributes across the user-set **tracking period** +(`TrackingStartDate` / `TrackingEndDate`). Applies ramp-up percentages to `GoalUnits` / `GoalCost`. + +The key difference: Phase 2 also updates `GoalUnits`/`GoalCost` on existing rows via an UPDATE +before the DELETE/INSERT sequence (to preserve ramp-up adjustments relative to the new period). + +--- + +## Ramp-Up Application + +```sql +-- Conceptually: +GoalCost = DistributedCost * RampUp -- for FromOpportunityToInitiative +GoalCost = GoalCost / OldRampUp * NewRampUp -- for RampUpChange +``` + +The SQL builder generates a CTE (`rp`) holding `(YearId, MonthId, RampUp)` values from the +`_rampUps` array, then LEFT JOINs fact rows to it. Rows with no matching ramp-up month (or +`RampUp = 1`) are left unchanged. + +`UpdateDetailCommittedByRampUp(double[] oldRampUps)` handles the ramp-up-change case: it +recalculates `GoalUnits`/`GoalCost` by dividing out old ramp-up and applying new ramp-up, +without re-running the full distribution. + +--- + +## SQL Builder Pattern + +`DistributionProcessChargeCodeSqlBuilder` (and GL equivalent) build multi-statement SQL batches +using `StringBuilder`. A single `DistributeAsync()` call may execute 3–7 SQL statements in +sequence via `QueryMultipleReturnLastAsync` — the return value is taken from the **last** result +set (a summary row). + +Embedded SQL resources loaded from `/DistributionProcess/ChargeCode/SQL/` or `/GL/SQL/`: +- `DetailSourceDataMultiplier.sql` — creates temp data using a period multiplier +- `DeclareVariables.sql` — Snowflake DECLARE block for scripting +- Various INSERT templates + +`IsDistributedPeriodsEqual()` short-circuits the re-distribution if goal and tracking periods +are identical — in that case only the ramp-up UPDATE runs, not the full DELETE/INSERT. + +--- + +## DistributionMethod Enum + +The `DistributionMethod` on `StrategicOpportunity.BaselineDistribution` controls how baseline +costs are spread across distributed months. The SQL builder switches on this value to select +the appropriate distribution formula (equal split, weighted, etc.). + +--- + +## Key Files + +| File | Purpose | +|---|---| +| `DistributionProcessFactory.cs` | Creates typed distributor instances via `ActivatorUtilities` | +| `IDistributionProcessFactory.cs` | DI interface | +| `ChargeCode/DistributionProcessChargeCode.cs` | Executes distribution against Snowflake | +| `ChargeCode/DistributionProcessChargeCodeSqlBuilder.cs` | Builds multi-statement SQL batches | +| `GL/DistributionProcessGL.cs` | GL equivalent | +| `GL/DistributionProcessGLSqlBuilder.cs` | GL SQL builder | +| `Models/SqlBuilderResult.cs` | Carries `Sql`, `QueryCount`, `Parameters` for multi-statement execution | +| `Models/YearMonthStruct.cs` | Lightweight struct for period iteration | diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/DistributionProcessFactory.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/DistributionProcessFactory.cs new file mode 100644 index 0000000..3924926 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/DistributionProcessFactory.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.DependencyInjection; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.ChargeCode; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.GL; +using System; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess +{ + public class DistributionProcessFactory : IDistributionProcessFactory + { + private readonly IServiceProvider _serviceProvider; + + public DistributionProcessFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public DistributionProcessChargeCode CreateDistributionProcessChargeCode(long opportunityId, Period baselinePeriod, Period goalPeriod, Period trackingPeriod, double[] rampUps) + { + return ActivatorUtilities.CreateInstance(_serviceProvider, opportunityId, baselinePeriod, goalPeriod, trackingPeriod, rampUps); + } + + public DistributionProcessGL CreateDistributionProcessGL(long opportunityId, Period baselinePeriod, Period goalPeriod, Period trackingPeriod, double[] rampUps) + { + return ActivatorUtilities.CreateInstance(_serviceProvider, opportunityId, baselinePeriod, goalPeriod, trackingPeriod, rampUps); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/DistributionGLSummary.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/DistributionGLSummary.cs new file mode 100644 index 0000000..fd10e45 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/DistributionGLSummary.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.GL +{ + public class DistributionGLSummary + { + public double ImprovementSum { get; set; } + public double EstimatedGoalSum { get; set; } + public double CommittedSum { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/DistributionProcessGL.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/DistributionProcessGL.cs new file mode 100644 index 0000000..ee92689 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/DistributionProcessGL.cs @@ -0,0 +1,56 @@ +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.GL +{ + public class DistributionProcessGL + { + const string REQUEST_TYPE = "Strategic GL Opportunity Detail"; + + private readonly ISnowflakeDatabaseContext _snowflakeDatabaseContext; + private readonly DistributionProcessGLSqlBuilder _sqlBuilder; + + protected readonly long _opportunityId; + protected readonly Period _baselinePeriod; + protected readonly Period _goalPeriod; + protected readonly Period _trackingPeriod; + protected readonly double[] _rampUps; + + public DistributionProcessGL(ISnowflakeDatabaseContext snowflakeDatabaseContext, long opportunityId, Period baselinePeriod, Period goalPeriod, Period trackingPeriod, double[] rampUps) + { + _snowflakeDatabaseContext = snowflakeDatabaseContext; + _opportunityId = opportunityId; + _baselinePeriod = baselinePeriod; + _goalPeriod = goalPeriod; + _trackingPeriod = trackingPeriod; + _rampUps = rampUps; + + _sqlBuilder = new DistributionProcessGLSqlBuilder(opportunityId, baselinePeriod, goalPeriod, trackingPeriod, rampUps); + } + + public async Task DistributeAsync(DistributionMethod methodType, DistributionStage stage) + { + var sqlBuilderResult = _sqlBuilder.BuildInsertDistributedSql(methodType, stage); + var result = await _snowflakeDatabaseContext.QueryMultipleReturnLastAsync(sqlBuilderResult.Sql, REQUEST_TYPE, sqlBuilderResult.QueryCount, sqlBuilderResult.Parameters); + return result.FirstOrDefault(); + } + + public async Task GetDistributionSummaryAsync(bool isInitiative) + { + var sqlBuilderResult = _sqlBuilder.BuildDistributionSummarySql(isInitiative); + var result = await _snowflakeDatabaseContext.QueryMultipleReturnLastAsync(sqlBuilderResult.Sql, REQUEST_TYPE, sqlBuilderResult.QueryCount, sqlBuilderResult.Parameters); + return result.FirstOrDefault(); + } + + public async Task UpdateDetailCommittedByRampUp(double[] oldRampUps) + { + var sqlBuilderResult = _sqlBuilder.BuildUpdateDetailCommittedByRampUpSql(oldRampUps); + var result = await _snowflakeDatabaseContext.QueryMultipleReturnLastAsync(sqlBuilderResult.Sql, REQUEST_TYPE, sqlBuilderResult.QueryCount, sqlBuilderResult.Parameters); + return result.FirstOrDefault(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/DistributionProcessGLSqlBuilder.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/DistributionProcessGLSqlBuilder.cs new file mode 100644 index 0000000..b80aa79 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/DistributionProcessGLSqlBuilder.cs @@ -0,0 +1,409 @@ +using Dapper; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.Models; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.GL +{ + public class DistributionProcessGLSqlBuilder + { + private readonly Assembly _assembly; + private readonly long _opportunityId; + private readonly Period _baselinePeriod; + private readonly Period _goalPeriod; + private readonly Period _trackingPeriod; + private readonly double[] _rampUps; + + public DistributionProcessGLSqlBuilder( + long opportunityId, + Period baselinePeriod, + Period goalPeriod, + Period trackingPeriod, + double[] rampUps) + { + _assembly = Assembly.GetExecutingAssembly(); + _opportunityId = opportunityId; + _baselinePeriod = baselinePeriod; + _goalPeriod = goalPeriod; + _trackingPeriod = trackingPeriod; + _rampUps = rampUps; + } + + public SqlBuilderResult BuildInsertDistributedSql(DistributionMethod methodType, DistributionStage stage) + { + var isInitiative = GetIsInitiative(stage); + var distributedPeriod = GetDistributedPeriod(isInitiative); + var sqlRampUpTable = GetRampUpTableSql(stage == DistributionStage.FromInitiativeToOpportunity ? _trackingPeriod : distributedPeriod, _rampUps, "rp"); + + var sbQuery = new StringBuilder(); + var queryCount = (byte)0; + + if (stage == DistributionStage.FromOpportunityToInitiative || stage == DistributionStage.FromInitiativeToOpportunity || stage == DistributionStage.InitiativeChangeTrackingPeriod) + { + if (!string.IsNullOrEmpty(sqlRampUpTable)) + { + Func convertFunc = stage == DistributionStage.FromOpportunityToInitiative ? + GetFromGoalToCommittedSql : + GetFromCommittedToGoalSql; + sbQuery.AppendLine($@" + UPDATE {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} AS tt + SET + tt.GoalAmount = st.GoalAmount + FROM ( + SELECT + RowId, + (CASE WHEN rp.RampUp IS NULL OR rp.RampUp = 1 THEN GoalAmount ELSE {convertFunc("GoalAmount", "DistributedAmount", "rp.RampUp")} END) AS GoalAmount + FROM {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} AS sd + LEFT JOIN {sqlRampUpTable} ON rp.YearId = sd.FiscalYearId AND rp.MonthId = sd.FiscalMonthId + WHERE OpportunityId = @OpportunityId AND IsBaseline = False + ) AS st + WHERE st.RowId = tt.RowId; + "); + queryCount += 1; + } + } + + if (stage == DistributionStage.FromInitiativeToOpportunity) + { + sbQuery.AppendLine($@" + UPDATE {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} AS tt + SET + tt.Amount = 0 + WHERE tt.OpportunityId = @OpportunityId AND tt.IsBaseline = False; + "); + queryCount += 1; + } + + if (!((stage == DistributionStage.FromOpportunityToInitiative || stage == DistributionStage.FromInitiativeToOpportunity) && IsDistributedPeriodsEqual())) + { + if (IsUseMultiplier(stage)) + { + sbQuery.AppendLine(ReadResource("DetailSourceDataMultiplier.sql")); + queryCount += 3; // DetailSourceDataMultiplier.sql contains 3 statements: CREATE TEMP TABLE, INSERT, SELECT + } + sbQuery.AppendLine($"DELETE FROM {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} WHERE OpportunityId = @OpportunityId AND IsBaseline = False;"); + sbQuery.AppendLine("DECLARE"); + sbQuery.AppendLine(ReadResource("DeclareVariables.sql")); + sbQuery.AppendLine("BEGIN"); + sbQuery.AppendLine($@" + {GetInsertSql()} + WITH + {ReadResource("DatePeriodCTE.sql")}, + {ReadResource(GetDetailDataCteFile(methodType))} + {GetSelectSql(stage)}; + "); + sbQuery.AppendLine("END;"); + queryCount += 2; // DELETE statement + DECLARE/BEGIN.../END block each count as 1 Snowflake statement + } + + sbQuery.AppendLine(GetSummarySql(isInitiative)); + queryCount += 1; + + var parameters = GetParameters(sbQuery, queryCount, distributedPeriod); + + return new SqlBuilderResult() + { + Sql = sbQuery.ToString().SnowflakeSyntax(), + QueryCount = queryCount, + Parameters = parameters + }; + } + + public SqlBuilderResult BuildDistributionSummarySql(bool isInitiative) + { + var sbQuery = new StringBuilder(GetSummarySql(isInitiative)); + var queryCount = (byte)1; + + var parameters = GetParameters(sbQuery, queryCount, GetDistributedPeriod(isInitiative)); + + return new SqlBuilderResult() + { + Sql = sbQuery.ToString().SnowflakeSyntax(), + QueryCount = queryCount, + Parameters = parameters + }; + } + + public SqlBuilderResult BuildUpdateDetailCommittedByRampUpSql(double[] oldRampUps) + { + var isInitiative = true; + var queryCount = (byte)0; + var sbQuery = new StringBuilder(); + + var distributedPeriod = GetDistributedPeriod(isInitiative); + + var sqlOldRampUpTable = GetRampUpTableSql(distributedPeriod, oldRampUps, "orp"); + var sqlNewRampUpTable = GetRampUpTableSql(distributedPeriod, _rampUps, "nrp"); + + if (!(string.IsNullOrEmpty(sqlOldRampUpTable) && string.IsNullOrEmpty(sqlNewRampUpTable))) + { + if (string.IsNullOrEmpty(sqlOldRampUpTable)) sqlOldRampUpTable = GetRampUpTableSql(distributedPeriod, oldRampUps, "orp", true); + if (string.IsNullOrEmpty(sqlNewRampUpTable)) sqlNewRampUpTable = GetRampUpTableSql(distributedPeriod, _rampUps, "nrp", true); + + sbQuery.AppendLine($@" + UPDATE {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} AS tt + SET + tt.GoalAmount = st.GoalAmount + FROM ( + SELECT + RowId, + ({GetFromCommittedToGoalOnRampUpUpdateSql("GoalAmount", "DistributedAmount", "orp.RampUp", "nrp.RampUp")}) AS GoalAmount + FROM {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} AS sd + LEFT JOIN {sqlOldRampUpTable} ON orp.YearId = sd.FiscalYearId AND orp.MonthId = sd.FiscalMonthId + LEFT JOIN {sqlNewRampUpTable} ON nrp.YearId = sd.FiscalYearId AND nrp.MonthId = sd.FiscalMonthId + WHERE OpportunityId = @OpportunityId AND IsBaseline = False + ) AS st + WHERE st.RowId = tt.RowId; + "); + queryCount += 1; + } + + sbQuery.AppendLine(GetSummarySql(isInitiative)); + queryCount += 1; + + var parameters = GetParameters(sbQuery, queryCount, GetDistributedPeriod(isInitiative)); + + return new SqlBuilderResult() + { + Sql = sbQuery.ToString().SnowflakeSyntax(), + QueryCount = queryCount, + Parameters = parameters + }; + } + + private string ReadResource(string fileName) + { + return _assembly.ReadResource($"GL.SQL.{fileName}"); + } + + private bool GetIsInitiative(DistributionStage stage) + { + return stage switch + { + DistributionStage.FromSourceToOpportunity => false, + DistributionStage.OpportunityChangeGoalPeriod => false, + DistributionStage.FromOpportunityToInitiative => true, + DistributionStage.InitiativeChangeTrackingPeriod => true, + DistributionStage.FromInitiativeToOpportunity => false, + _ => false + }; + } + + private Period GetDistributedPeriod(bool isInitiative) + { + return isInitiative ? _trackingPeriod : _goalPeriod; + } + + private bool IsDistributedPeriodsEqual() + { + return _goalPeriod.Start.Year == _trackingPeriod.Start.Year && + _goalPeriod.Start.Month == _trackingPeriod.Start.Month && + _goalPeriod.End.Year == _trackingPeriod.End.Year && + _goalPeriod.End.Month == _trackingPeriod.End.Month; + } + + private bool IsUseMultiplier(DistributionStage stage) + { + return stage == DistributionStage.FromOpportunityToInitiative || stage == DistributionStage.FromInitiativeToOpportunity || stage == DistributionStage.InitiativeChangeTrackingPeriod || stage == DistributionStage.OpportunityChangeGoalPeriod; + } + + private string GetSelectSql(DistributionStage stage) + { + var distributionPeriod = GetDistributedPeriod(GetIsInitiative(stage)); + var sqlRampUpTable = GetRampUpTableSql(distributionPeriod, _rampUps, "rp", true); + var isUseMultiplier = IsUseMultiplier(stage); + + string goalValuesSql = "dd.Amount AS GoalAmount"; + + if (!string.IsNullOrEmpty(sqlRampUpTable) && isUseMultiplier) + { + if (stage == DistributionStage.FromInitiativeToOpportunity) + { + goalValuesSql = "dd.Amount * MultiplierAmount AS GoalAmount"; + } + else + { + goalValuesSql = $@" + (CASE WHEN mlt.MultiplierAmount IS NULL THEN dd.Amount ELSE dd.Amount * mlt.MultiplierAmount END) AS NewGoalAmount, + (CASE WHEN rp.RampUp IS NULL OR rp.RampUp = 1 THEN NewGoalAmount ELSE {GetFromGoalToCommittedSql("NewGoalAmount", "dd.Amount", "rp.RampUp")} END) AS GoalAmount + "; + } + } + + string joinSql = !string.IsNullOrEmpty(sqlRampUpTable) && isUseMultiplier ? $@" + LEFT JOIN DetailSourceDataMultiplier AS mlt ON + mlt.EntityID = dd.EntityID AND + mlt.DepartmentID = dd.DepartmentID AND + mlt.AccountId = dd.AccountId AND + mlt.AccountType IS NOT DISTINCT FROM dd.AccountType + LEFT JOIN {sqlRampUpTable} ON rp.YearId = dd.YearId AND rp.MonthId = dd.MonthId + " : string.Empty; + + var sql = $@" + SELECT + {GetColumnsList()} + FROM ( + SELECT + @OpportunityId AS OpportunityId, + False AS IsBaseline, + dd.YearId AS FiscalYearId, + dd.MonthId AS FiscalMonthId, + dd.EntityID, + dd.DepartmentId, + dd.AccountId, + dd.AccountType, + 0 AS Amount, + dd.Amount AS DistributedAmount, + {goalValuesSql} + FROM DetailNewAllData AS dd + {joinSql} + ) + "; + return sql; + } + + private string GetInsertSql() + { + return $@" + INSERT INTO {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} ( + {GetColumnsList()} + ) + "; + } + + private string GetColumnsList() + { + return @" + OpportunityId, + IsBaseline, + FiscalYearId, + FiscalMonthId, + EntityId, + DepartmentId, + AccountId, + AccountType, + Amount, + DistributedAmount, + GoalAmount"; + } + + private string GetSummarySql(bool isInitiative) + { + if (isInitiative) + { + return $@" + SELECT + 0::FLOAT AS ImprovementSum, + 0::FLOAT AS EstimatedGoalSum, + COALESCE(SUM(GoalAmount), 0)::FLOAT AS CommittedSum + FROM {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} AS dl + WHERE OpportunityId = @OpportunityId AND IsBaseline = False; + "; + } + else + { + var sqlRampUpTable = GetRampUpTableSql(GetDistributedPeriod(isInitiative), _rampUps, "rp"); + + var estimatedGoalSumSql = !string.IsNullOrEmpty(sqlRampUpTable) ? + $"COALESCE(SUM((CASE WHEN rp.RampUp IS NULL OR rp.RampUp = 1 THEN GoalAmount ELSE {GetFromGoalToCommittedSql("GoalAmount", "DistributedAmount", "rp.RampUp")} END)), 0)::FLOAT AS EstimatedGoalSum" : + "COALESCE(SUM(GoalAmount), 0)::FLOAT AS EstimatedGoalSum"; + var joinSql = !string.IsNullOrEmpty(sqlRampUpTable) ? + $"LEFT JOIN {sqlRampUpTable} ON rp.YearId = sd.FiscalYearId AND rp.MonthId = sd.FiscalMonthId" : + string.Empty; + + return $@" + SELECT + COALESCE(SUM(DistributedAmount - GoalAmount), 0)::FLOAT AS ImprovementSum, + {estimatedGoalSumSql}, + 0::FLOAT AS CommittedSum + FROM {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} AS sd + {joinSql} + WHERE OpportunityId = @OpportunityId AND IsBaseline = False; + "; + } + } + + private string GetDetailDataCteFile(DistributionMethod methodType) + { + return methodType == DistributionMethod.Monthly ? "DetailDataMonthlyCTE.sql" : "DetailDataAverageCTE.sql"; + } + + private string GetRampUpTableSql(Period period, double[] rampUps, string tableName = "RampUpTable", bool isReturnFakeOnEmpty = false) + { + if (rampUps.Length == 0) + { + if (isReturnFakeOnEmpty) + { + return $@" + (SELECT + {period.Start.Year} AS YearId, + {period.Start.Month} AS MonthId, + 1.0 AS RampUp + ) AS {tableName} (YearId, MonthId, RampUp) + "; + } + return string.Empty; + } + var baselineYearMonthList = YearMonthStruct.GetList(new YearMonthStruct(period.Start), new YearMonthStruct(period.End)); + return $@" + (VALUES + {string.Join($",{Environment.NewLine}", baselineYearMonthList.Take(rampUps.Length).Select((x, ind) => $"({x.Year}, {x.Month}, {rampUps[ind]})"))} + ) AS {tableName} (YearId, MonthId, RampUp) + "; + } + + private DynamicParameters GetParameters(StringBuilder sbQuery, byte queryCount, Period distributionPeriod) + { + var parameters = new Dictionary + { + { "@OpportunityId", _opportunityId.ToString() }, + { "@BaselineDateStart", $"'{_baselinePeriod.Start:yyyy-MM-dd}'" }, + { "@BaselineDateEnd", $"'{_baselinePeriod.End:yyyy-MM-dd}'" }, + { "@GoalDateStart", $"'{distributionPeriod.Start:yyyy-MM-dd}'" }, + { "@GoalDateEnd", $"'{distributionPeriod.End:yyyy-MM-dd}'" } + }; + + foreach (var x in parameters) + { + sbQuery = sbQuery.Replace(x.Key, x.Value); + } + + var queryOneParams = new DynamicParameters(); + queryOneParams.Add("MULTI_STATEMENT_COUNT", queryCount); + + return queryOneParams; + } + + private string GetFromGoalToCommittedSql(string goalColumn, string distributedColumn, string rampUpColumn) + { + return $"({distributedColumn} - ({distributedColumn} - {goalColumn}) * {rampUpColumn})"; + } + + private string GetFromCommittedToGoalSql(string goalColumn, string distributedColumn, string rampUpColumn) + { + return $"(({goalColumn} - {distributedColumn} * (1 - {rampUpColumn})) / {rampUpColumn})"; + } + + private string GetFromCommittedToCommittedSql(string goalColumn, string distributedColumn, string oldRampUpColumn, string newRampUpColumn) + { + return GetFromGoalToCommittedSql(GetFromCommittedToGoalSql(goalColumn, distributedColumn, oldRampUpColumn), distributedColumn, newRampUpColumn); + } + + private string GetFromCommittedToGoalOnRampUpUpdateSql(string goalColumn, string distributedColumn, string rampUpOldColumn, string rampUpNewColumn) + { + return @$"CASE + WHEN ({rampUpOldColumn} IS NULL OR {rampUpOldColumn} = 1 ) AND ({rampUpNewColumn} IS NULL OR {rampUpNewColumn} = 1) THEN {goalColumn} + WHEN {rampUpOldColumn} <> 1 AND ({rampUpNewColumn} IS NULL OR {rampUpNewColumn} = 1) THEN {GetFromCommittedToGoalSql(goalColumn, distributedColumn, rampUpOldColumn)} + WHEN ({rampUpOldColumn} IS NULL OR {rampUpOldColumn} = 1 ) AND {rampUpNewColumn} <> 1 THEN {GetFromGoalToCommittedSql(goalColumn, distributedColumn, rampUpNewColumn)} + WHEN {rampUpOldColumn} <> 1 AND {rampUpNewColumn} <> 1 THEN {GetFromCommittedToCommittedSql(goalColumn, distributedColumn, rampUpOldColumn, rampUpNewColumn)} + ELSE {goalColumn} + END"; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DatePeriodCTE.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DatePeriodCTE.sql new file mode 100644 index 0000000..219b751 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DatePeriodCTE.sql @@ -0,0 +1,36 @@ +BasePeriod AS ( + SELECT + MONTH(monthDate) AS MonthId, + YEAR(monthDate) AS YearId + FROM ( + SELECT + DATEADD('month', seq4(), @BaselineDateStart) AS monthDate + FROM TABLE(GENERATOR(ROWCOUNT => @BaselineMonthCount)) + ) +), +GoalPeriod AS ( + SELECT + MONTH(monthDate) AS MonthId, + YEAR(monthDate) AS YearId + FROM ( + SELECT + DATEADD('month', seq4(), @GoalDateStart) AS monthDate + FROM TABLE(GENERATOR(ROWCOUNT => @GoalMonthCount)) + ) +), +MonthData AS ( + SELECT + COALESCE(base.MonthId, goal.MonthId) AS MonthId, + COALESCE(base.monthCount, 0) AS BaselineMonthCount, + COALESCE(goal.monthCount, 0) AS GoalMonthCount, + (case when GoalMonthCount > 0 and BaselineMonthCount > 0 then True else False end)::BOOLEAN AS IsCross + FROM (SELECT MonthId, COUNT(*) AS monthCount FROM GoalPeriod group by MonthId) AS goal + FULL JOIN (SELECT MonthId, COUNT(*) AS monthCount FROM BasePeriod group by MonthId) AS base + ON base.MonthId = goal.MonthId +), +MonthDataSummary AS ( + SELECT + SUM(case when IsCross = True then GoalMonthCount else 0 end) AS CrossCount, + SUM(case when IsCross = True then 0 else GoalMonthCount end) AS NotCrossCount, + FROM MonthData +) \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DeclareVariables.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DeclareVariables.sql new file mode 100644 index 0000000..d42b581 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DeclareVariables.sql @@ -0,0 +1,2 @@ +GoalMonthCount INTEGER := (SELECT DATEDIFF('month', @GoalDateStart, @GoalDateEnd) + 1); +BaselineMonthCount INTEGER := (SELECT DATEDIFF('month', @BaselineDateStart, @BaselineDateEnd) + 1); \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DetailDataAverageCTE.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DetailDataAverageCTE.sql new file mode 100644 index 0000000..1f0090d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DetailDataAverageCTE.sql @@ -0,0 +1,28 @@ +DetailData AS ( + SELECT + EntityID, + DepartmentID, + AccountId, + AccountType, + (CASE WHEN @BaselineMonthCount = 0 THEN 0 ELSE (SUM(Amount)/@BaselineMonthCount) END) AS Amount + FROM cci.FactStrategicGLDetail + WHERE IsBaseline = True AND OpportunityId = @OpportunityId + GROUP BY + EntityID, + DepartmentID, + AccountId, + AccountType +), + +DetailNewAllData AS ( + SELECT + gp.YearId, + gp.MonthId, + dd.EntityID, + dd.DepartmentID, + dd.AccountId, + dd.AccountType, + dd.Amount + FROM DetailData AS dd + INNER JOIN GoalPeriod AS gp +) \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DetailDataMonthlyCTE.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DetailDataMonthlyCTE.sql new file mode 100644 index 0000000..3bf118f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DetailDataMonthlyCTE.sql @@ -0,0 +1,103 @@ +DetailMonthData AS ( + SELECT + FiscalMonthId AS MonthId, + EntityID, + DepartmentID, + AccountId, + AccountType, + SUM(Amount) AS SumMonthAmount, + SUM(md.BaselineMonthCount) AS SumBaselineMonthCount, + SUM(md.GoalMonthCount) AS SumGoalMonthCount, + (CASE WHEN SumBaselineMonthCount = 0 OR SumGoalMonthCount = 0 THEN 0 ELSE ((SumMonthAmount/SumBaselineMonthCount) * SumGoalMonthCount) END) AS SumGoalMonthAmount, + md.IsCross + FROM cci.FactStrategicGLDetail AS sd + INNER JOIN MonthData AS md ON md.MonthId = sd.FiscalMonthId + WHERE IsBaseline = True AND OpportunityId = @OpportunityId + GROUP BY + IsCross, + FiscalMonthId, + EntityID, + DepartmentID, + AccountId, + AccountType +), + +DetailTotalData AS ( + SELECT + EntityID, + DepartmentID, + AccountId, + AccountType, + SUM(dmd.SumMonthAmount) AS SumAmount, + (CASE WHEN @BaselineMonthCount = 0 THEN 0 ELSE (SumAmount/@BaselineMonthCount) END) AS AverageAmount, + SUM(case when IsCross = True then dmd.SumGoalMonthAmount else 0 end) AS CrossSumGoalMonthAmount, + SUM(case when IsCross = True then 1 else 0 end) AS CrossMonthCount + FROM DetailMonthData AS dmd + GROUP BY + EntityID, + DepartmentID, + AccountId, + AccountType +), + +DetailNewCross AS ( + SELECT + md.MonthId AS MonthId, + dmd.EntityID, + dmd.DepartmentID, + dmd.AccountId, + dmd.AccountType, + (case when dtd.CrossSumGoalMonthAmount = 0 then 0 else ((dtd.AverageAmount * mds.CrossCount) * (dmd.SumGoalMonthAmount / dtd.CrossSumGoalMonthAmount)/md.GoalMonthCount) end) AS Amount + FROM MonthData AS md + INNER JOIN MonthDataSummary AS mds + INNER JOIN DetailMonthData AS dmd on + dmd.MonthId = md.MonthId + AND md.IsCross = True + INNER JOIN DetailTotalData AS dtd ON + dtd.EntityID = dmd.EntityID AND + dtd.DepartmentID = dmd.DepartmentID AND + dtd.AccountId = dmd.AccountId AND + dtd.AccountType = dmd.AccountType +), + +DetailNewNotCross AS ( + SELECT + md.MonthId AS MonthId, + EntityID, + DepartmentID, + AccountId, + AccountType, + (case when md.BaselineMonthCount = 0 then AverageAmount else 0.0 end) AS Amount + FROM MonthData AS md + CROSS JOIN DetailTotalData AS dtd + WHERE md.GoalMonthCount > 0 + AND (md.BaselineMonthCount = 0 OR dtd.CrossMonthCount = 0) +), + +DetailData AS ( + SELECT + dtd.MonthId, + dtd.EntityID, + dtd.DepartmentID, + dtd.AccountId, + dtd.AccountType, + dtd.Amount + FROM ( + SELECT * FROM DetailNewCross + UNION + SELECT * FROM DetailNewNotCross + ) AS dtd +), + +DetailNewAllData AS ( + SELECT + gp.YearId, + gp.MonthId, + dd.EntityID, + dd.DepartmentID, + dd.AccountId, + dd.AccountType, + dd.Amount + FROM DetailData AS dd + INNER JOIN GoalPeriod AS gp ON gp.MonthId = dd.MonthId +) \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DetailSourceDataMultiplier.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DetailSourceDataMultiplier.sql new file mode 100644 index 0000000..deee678 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/GL/SQL/DetailSourceDataMultiplier.sql @@ -0,0 +1,35 @@ +DROP TABLE IF EXISTS DetailSourceDataMultiplier; +CREATE TEMPORARY TABLE DetailSourceDataMultiplier ( + EntityId INT NOT NULL, + DepartmentId INT NOT NULL, + AccountId INT NOT NULL, + AccountType TINYINT, + MultiplierAmount FLOAT NOT NULL +); + +INSERT INTO DetailSourceDataMultiplier +WITH DetailSourceDataMultiplierCTE AS ( + SELECT + EntityID, + DepartmentID, + AccountId, + AccountType, + SUM(DistributedAmount) AS AmountDistributed, + SUM(GoalAmount) AS AmountGoal, + (CASE WHEN AmountGoal = 0 OR AmountDistributed = 0 THEN 1 ELSE AmountGoal / AmountDistributed END) AS MultiplierAmount + FROM cci.FactStrategicGLDetail + WHERE OpportunityId = @OpportunityId AND IsBaseline = False + GROUP BY + EntityID, + DepartmentID, + AccountId, + AccountType +) + +SELECT + EntityID, + DepartmentID, + AccountId, + AccountType, + MultiplierAmount +FROM DetailSourceDataMultiplierCTE; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/IDistributionProcessFactory.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/IDistributionProcessFactory.cs new file mode 100644 index 0000000..6ea90b4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/IDistributionProcessFactory.cs @@ -0,0 +1,13 @@ +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.ChargeCode; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.GL; +using System; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess +{ + public interface IDistributionProcessFactory + { + public DistributionProcessChargeCode CreateDistributionProcessChargeCode(long opportunityId, Period baselinePeriod, Period goalPeriod, Period trackingPeriod, double[] rampUps); + public DistributionProcessGL CreateDistributionProcessGL(long opportunityId, Period baselinePeriod, Period goalPeriod, Period trackingPeriod, double[] rampUps); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/Models/SqlBuilderResult.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/Models/SqlBuilderResult.cs new file mode 100644 index 0000000..8217ebe --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/Models/SqlBuilderResult.cs @@ -0,0 +1,16 @@ +using Dapper; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.Models +{ + public class SqlBuilderResult + { + public string Sql { get; set; } + public byte QueryCount { get; set; } + public DynamicParameters Parameters { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/Models/YearMonthStruct.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/Models/YearMonthStruct.cs new file mode 100644 index 0000000..038a3e6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/DistributionProcess/Models/YearMonthStruct.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.Models +{ + public struct YearMonthStruct : IComparable, IEquatable + { + public short Year { get; private set; } + public byte Month { get; private set; } + + public YearMonthStruct(short year, byte month) + { + Year = year; + Month = month; + } + + public YearMonthStruct(YearMonthStruct x) + { + Year = x.Year; + Month = x.Month; + } + + public YearMonthStruct(DateOnly x) + { + Year = (short)x.Year; + Month = (byte)x.Month; + } + + public static YearMonthStruct operator ++(YearMonthStruct operand) + { + return operand + 1; + } + + public static YearMonthStruct operator +(YearMonthStruct x, int month) + { + int totalMonths = (x.Year * 12 + (x.Month - 1)) + month; + short newYear = (short)(totalMonths / 12); + byte newMonth = (byte)((totalMonths % 12) + 1); + return new YearMonthStruct(newYear, newMonth); + } + + public static bool operator <=(YearMonthStruct left, YearMonthStruct right) + { + return left.GetMonths() <= right.GetMonths(); + } + + public static bool operator >=(YearMonthStruct left, YearMonthStruct right) + { + return left.GetMonths() >= right.GetMonths(); + } + + public static bool operator <(YearMonthStruct left, YearMonthStruct right) + { + return left.GetMonths() < right.GetMonths(); + } + + public static bool operator >(YearMonthStruct left, YearMonthStruct right) + { + return left.GetMonths() > right.GetMonths(); + } + + public static bool operator ==(YearMonthStruct left, YearMonthStruct right) + { + return left.Equals(right); + } + + public static bool operator !=(YearMonthStruct left, YearMonthStruct right) + { + return !left.Equals(right); + } + + public static IEnumerable GetList(YearMonthStruct start, YearMonthStruct end) + { + for (YearMonthStruct x = start; x <= end; x++) + { + yield return x; + } + } + + public static int GetListCount(YearMonthStruct start, YearMonthStruct end) + { + return GetList(start, end).Count(); + } + + public bool Equals(YearMonthStruct other) + { + return Year == other.Year && Month == other.Month; + } + + public override bool Equals(object obj) + { + return obj is YearMonthStruct other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Year, Month); + } + + public int GetMonths() + { + return Year * 12 + (Month - 1); + } + + public int CompareTo(YearMonthStruct other) + { + if (Year != other.Year) + return Year.CompareTo(other.Year); + return Month.CompareTo(other.Month); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/GeneralStrategicOpportunityItem.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/GeneralStrategicOpportunityItem.cs new file mode 100644 index 0000000..936c3b2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/GeneralStrategicOpportunityItem.cs @@ -0,0 +1,24 @@ +using System; +using Strata.ContinuousImprovement.Biz.Generics; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class GeneralStrategicOpportunityItem + { + public long OpportunityId { get; set; } + public string Name { get; set; } + public string EncounterGroupName { get; set; } + public string PatientPopulationHPath { get; set; } + public long ExplorationPopulationId { get; set; } + public StrategicOpportunityTypes OpportunityType { get; set; } + public string OpportunityTypeName { get; set; } + public string Rollup1 { get; set; } + public string Rollup2 { get; set; } + public string Rollup3 { get; set; } + public string Population { get; set; } + public string AdditionalFilters { get; set; } + public string BaselinePeriod { get; set; } + public string StatusName { get; set; } + public DateOnly BaselineStartDate { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicInitiativeItem.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicInitiativeItem.cs new file mode 100644 index 0000000..c612a5b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicInitiativeItem.cs @@ -0,0 +1,17 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicInitiativeItem : GeneralStrategicOpportunityItem + { + public string TrackingPeriod { get; set; } + public double TotalCommitted { get; set; } + public double ProgressToDateCommitted { get; set; } + public double ProgressToDateActual { get; set; } + public double ProgressToDateVariance { get; set; } + public DateTime? LastRunTrackingDate { get; set; } + public byte LastRunTrackingStatus { get; set; } + public bool OnTrack { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicInitiativeUpdateDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicInitiativeUpdateDto.cs new file mode 100644 index 0000000..0ab5a9d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicInitiativeUpdateDto.cs @@ -0,0 +1,11 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicInitiativeUpdateDto + { + public string Name { get; set; } + public DateOnly TrackingStartDate { get; set; } + public DateOnly TrackingEndDate { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityChargeCodeDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityChargeCodeDto.cs new file mode 100644 index 0000000..793e700 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityChargeCodeDto.cs @@ -0,0 +1,12 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityChargeCodeDto: StrategicOpportunityDto + { + public string PatientPopulationHPath { get; set; } + public long ExplorationPopulationId { get; set; } + public string ChargeCodeHPath { get; set; } + public int StrataId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityChargeCodeUpdateDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityChargeCodeUpdateDto.cs new file mode 100644 index 0000000..8b514e9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityChargeCodeUpdateDto.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityChargeCodeUpdateDto + { + public StrategicOpportunityChargeCodeDto Opportunity { get; set; } + public bool ShouldPopulateOpportunityDetails { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityDetailData.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityDetailData.cs new file mode 100644 index 0000000..6f76cad --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityDetailData.cs @@ -0,0 +1,275 @@ +using MoreLinq; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityDetailDimension + { + public static readonly StrategicOpportunityDetailDimension Encounter = new StrategicOpportunityDetailDimension("encounter"); + public static readonly StrategicOpportunityDetailDimension Entity = new StrategicOpportunityDetailDimension("entity"); + public static readonly StrategicOpportunityDetailDimension PrimaryServiceLine = new StrategicOpportunityDetailDimension("primaryServiceLine"); + public static readonly StrategicOpportunityDetailDimension CaseTypeFamily = new StrategicOpportunityDetailDimension("caseTypeFamily"); + public static readonly StrategicOpportunityDetailDimension Physician = new StrategicOpportunityDetailDimension("physician"); + public static readonly StrategicOpportunityDetailDimension PhysicianSpecialty = new StrategicOpportunityDetailDimension("physicianSpecialty"); + public static readonly StrategicOpportunityDetailDimension CostDriver = new StrategicOpportunityDetailDimension("costDriver"); + public static readonly StrategicOpportunityDetailDimension Department = new StrategicOpportunityDetailDimension("department"); + public static readonly StrategicOpportunityDetailDimension ChargeCode = new StrategicOpportunityDetailDimension("chargeCode"); + public static readonly StrategicOpportunityDetailDimension System = new StrategicOpportunityDetailDimension("system"); + public static readonly StrategicOpportunityDetailDimension GLAccount = new StrategicOpportunityDetailDimension("glAccount"); + public static readonly StrategicOpportunityDetailDimension GLRollup = new StrategicOpportunityDetailDimension("glRollup"); + + public string Value { get; private set; } + + public static StrategicOpportunityDetailDimension GetFromString(string value) + { + if (Encounter.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return Encounter; + else if (Entity.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return Entity; + else if (PrimaryServiceLine.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return PrimaryServiceLine; + else if (CaseTypeFamily.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return CaseTypeFamily; + else if (Physician.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return Physician; + else if (PhysicianSpecialty.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return PhysicianSpecialty; + else if (CostDriver.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return CostDriver; + else if (Department.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return Department; + else if (ChargeCode.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return ChargeCode; + else if (System.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return System; + else if (GLAccount.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return GLAccount; + else if (GLRollup.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) return GLRollup; + else return null; + } + + private StrategicOpportunityDetailDimension(string value) + { + Value = value; + } + + public override string ToString() + { + return Value; + } + } + + public class StrategicOpportunityDetailDimensionConfig + { + public string SqlFile { get; set; } + public string ColumnName { get; set; } + public string AliasColumnName { get; set; } + public bool IsKeyString { get; set; } + } + + public class StrategicOpportunityDetailData + { + public IEnumerable Data { get; set; } + public Dictionary> DimensionsData { get; set; } + public Dictionary EncounterData { get; set; } + } + + public class StrategicOpportunityDetailGrouped + { + public string Key { get; set; } + public bool IsBaseline { get; set; } + public double Units { get; set; } + public double Cost { get; set; } + public double GoalUnits { get; set; } + public double GoalCost { get; set; } + } + + public class StrategicOpportunityDetailGroupedWithParsedKey + { + public string ParsedKey { get; private set; } + public StrategicOpportunityDetailGrouped StrategicOpportunityDetailGrouped { get; private set; } + + public StrategicOpportunityDetailGroupedWithParsedKey(StrategicOpportunityDetailGrouped item, Dictionary> dimensionsData, IEnumerable layoutRows) + { + StrategicOpportunityDetailGrouped = item; + StringBuilder key = new StringBuilder(); + var keys = item.Key.Split('|'); + + layoutRows.ForEach((row, i) => + { + var value = dimensionsData[row].FirstOrDefault(v => v.Key == keys[i]).Value; + key.Append($"{value}|{keys[i]}|"); + }); + + ParsedKey = key.ToString(); + } + } + + public class StrategicOpportunityGLDetailGoalValues + { + public long RowId { get; set; } + public double GoalAmount { get; set; } + } + public class StrategicOpportunityGLDetailData + { + public IEnumerable Data { get; set; } + public Dictionary> DimensionsData { get; set; } + } + + public class StrategicOpportunityGeneralInfo + { + public long OpportunityId { get; set; } + public byte OpportunityType { get; set; } + public string Name { get; set; } + public byte EstimatedTrackingDuration { get; set; } + public byte BaselineDuration { get; set; } + public DateOnly BaselineEndDate { get; set; } + public DateOnly TrackingStartDate { get; set; } + public DateOnly TrackingEndDate { get; set; } + public byte Status { get; set; } + public DateTime? LastRunTrackingDate { get; set; } + public byte LastRunTrackingStatus { get; set; } + public double TotalCommittedCost { get; set; } + public Guid OwnerGuid { get; set; } + public Guid ExecutiveSponsorGuid { get; set; } + public Guid CostLeaderGuid { get; set; } + public string TeamMembers { get; set; } = ""; + public IEnumerable Stakeholders { get; set; } + public IEnumerable Measures { get; set; } + public byte? AccountType { get; set; } + public bool? HasStatistic { get; set; } + public StrategicOpportunityGeneralInfo() { } + + public StrategicOpportunityGeneralInfo(StrategicOpportunity opportunity) + { + var baselineDuration = DateExtensions.GetMonthDifference(opportunity.BaselineStartDate, opportunity.BaselineEndDate, true); + + OpportunityId = opportunity.OpportunityId; + OpportunityType = (byte)opportunity.OpportunityType; + Name = opportunity.Name; + BaselineDuration = (byte)baselineDuration; + EstimatedTrackingDuration = opportunity.EstimatedTrackingDuration; + TrackingStartDate = opportunity.TrackingStartDate; + TrackingEndDate = opportunity.TrackingEndDate; + BaselineEndDate = opportunity.BaselineEndDate; + Status = opportunity.Status; + LastRunTrackingDate = opportunity.LastRunTrackingDate; + LastRunTrackingStatus = (byte)opportunity.LastRunTrackingStatus; + TotalCommittedCost = opportunity is StrategicOpportunityChargeCode cc ? cc.TotalCommittedCost : 0; + AccountType = opportunity is StrategicOpportunityGL gl ? (byte)gl.AccountType : null; + HasStatistic = opportunity is StrategicOpportunityGL glStat ? glStat.HasStatistic : null; + OwnerGuid = opportunity.OwnerGuid; + ExecutiveSponsorGuid = opportunity.ExecutiveSponsorGuid; + CostLeaderGuid = opportunity.CostLeaderGuid; + TeamMembers = opportunity.TeamMembers; + Stakeholders = opportunity.Stakeholders != null ? + opportunity.Stakeholders.Select(s => s.UserGuid) : + new List(); + Measures = opportunity.Measures?.Select(item => item.MapToStrategicOpportunityMeasureDto()) ?? []; + } + } + + public class StrategicOpportunityDrawerDetails + { + public string PopulationName { get; set; } + public List Populations { get; set; } + public Dictionary ChargeCodes { get; set; } + public string Filters { get; set; } + public string BaselinePeriod { get; set; } + public DateOnly TrackingStartDate { get; set; } + public DateOnly TrackingEndDate { get; set; } + public byte EstimatedTrackingDuration { get; set; } + } + + public class StrategicOpportunityDetailGoalValues + { + public long RowId { get; set; } + public double GoalUnits { get; set; } + public double GoalCost { get; set; } + } + + public class StrategicOpportunityChargeCodeAdjustment + { + public string NodeKey { get; set; } + public decimal? UnitsValue { get; set; } + public decimal? CostsValue { get; set; } + } + + public class StrategicOpportunityGLAdjustment + { + public string NodeKey { get; set; } + public decimal? GoalAmountValue { get; set; } + public byte? AccountType { get; set; } + } + + public class UpdateGLDetailsDto + { + public IEnumerable Adjustments { get; set; } + public IEnumerable Rows { get; set; } + } + + public class StrategicOpportunityEditData + { + public string Name { get; set; } + public StrategicOpportunityTypes OpportunityType { get; set; } + public int Rollup1Id { get; set; } + public int Rollup2Id { get; set; } + public int Rollup3Id { get; set; } + public string Filters { get; set; } + public DateOnly BaselineStartDate { get; set; } + public DateOnly BaselineEndDate { get; set; } + public DistributionMethod BaselineDistribution { get; set; } + public byte EstimatedTrackingDuration { get; set; } + public List Measures { get; set; } + public List Workbooks { get; set; } + } + + public class StrategicOpportunityChargeCodeEditData : StrategicOpportunityEditData + { + public string EncounterGroupName { get; set; } + public string PatientPopulationHPath { get; set; } + public long ExplorationPopulationId { get; set; } + public List PopulationMembers { get; set; } + public List ChargeCodeHPath { get; set; } + public List ChargeCodes { get; set; } + } + + public class StrategicOpportunityGLEditData : StrategicOpportunityEditData + { + public GLAccountType AccountType { get; set; } + public string DepartmentHPath { get; set; } + public string RevenueAccountHPath { get; set; } + public string ExpenseAccountHPath { get; set; } + public string StatisticAccountHPath { get; set; } + public bool HasStatistic { get; set; } + } + + public class PopulationMember + { + public int Id { get; set; } + public string Name { get; set; } + } + + public class StrategicOpportunityCompleteInitiativeData + { + public bool IsThresholdMonitoring { get; set; } + public Single AcceptableThreshold { get; set; } + } + + public class StrategicOpportunityGetDetailData + { + public string[] Dimensions { get; set; } + public DateOnly? DateStart { get; set; } + public DateOnly? DateEnd { get; set; } + + public void SetFirstAndLastDayOfMonth() + { + if (DateStart.HasValue) + { + DateStart = new DateOnly(DateStart.Value.Year, DateStart.Value.Month, 1); + } + if (DateEnd.HasValue) + { + var lastDay = DateTime.DaysInMonth(DateEnd.Value.Year, DateEnd.Value.Month); + DateEnd = new DateOnly(DateEnd.Value.Year, DateEnd.Value.Month, lastDay); + } + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityDto.cs new file mode 100644 index 0000000..43190db --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityDto.cs @@ -0,0 +1,34 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityDto + { + public long OpportunityId { get; set; } + public string Name { get; set; } + public StrategicOpportunityTypes OpportunityType { get; set; } + + public string OpportunityFiltersJSON { get; set; } + public StrategicOpportunityLayout Layout { get; set; } + + public int Rollup1Id { get; set; } + public int Rollup2Id { get; set; } + public int Rollup3Id { get; set; } + + public BaselineType BaselineType { get; set; } + public DistributionMethod BaselineDistribution { get; set; } + + public DateOnly BaselineStartDate { get; set; } + public DateOnly BaselineEndDate { get; set; } + + public byte EstimatedTrackingDuration { get; set; } + + public IEnumerable Measures { get; set; } + public IEnumerable WorkbookIds { get; set; } + + public int StrataId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityGLDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityGLDto.cs new file mode 100644 index 0000000..75909ec --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityGLDto.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityGLDto : StrategicOpportunityDto + { + public GLAccountType AccountType { get; set; } + public string DepartmentHPath { get; set; } + public string RevenueAccountHPath { get; set; } + public string ExpenseAccountHPath { get; set; } + public string StatisticAccountHPath { get; set; } + public bool HasStatistic { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityGLUpdateDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityGLUpdateDto.cs new file mode 100644 index 0000000..5975aff --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityGLUpdateDto.cs @@ -0,0 +1,8 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityGLUpdateDto + { + public StrategicOpportunityGLDto Opportunity { get; set; } + public bool ShouldPopulateOpportunityDetails { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityItem.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityItem.cs new file mode 100644 index 0000000..95b04fc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityItem.cs @@ -0,0 +1,12 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityItem : GeneralStrategicOpportunityItem + { + public byte EstimatedTrackDurationMonths { get; set; } + public double TotalImprovement { get; set; } + public double EstimatedGoal { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityMeasureDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityMeasureDto.cs new file mode 100644 index 0000000..99854f4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityMeasureDto.cs @@ -0,0 +1,18 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using System; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityMeasureDto + { + public long MeasureId { get; set; } + public int MeasureType { get; set; } + public string MeasureName { get; set; } + public byte DesiredResult { get; set; } + public Single AcceptableThreshold { get; set; } + public string FiltersJSON { get; set; } + public byte MeasureGroup { get; set; } + public double AverageValue { get; set; } + public int StrataId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityRampUpDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityRampUpDto.cs new file mode 100644 index 0000000..02d8261 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityRampUpDto.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityRampUpDto + { + [Required] + public long OpportunityId { get; set; } + [Required] + public byte MonthNumber { get; set; } + [Required] + public double Percentage { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunitySourceDbTable.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunitySourceDbTable.cs new file mode 100644 index 0000000..cb95d01 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunitySourceDbTable.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunitySourceDbTable + { + public static readonly StrategicOpportunitySourceDbTable FactPatientEncounterSummary = new("clientdss.FactPatientEncounterSummary.dsspes"); + public static readonly StrategicOpportunitySourceDbTable FactPatientBillingLineItemDetail = new ("dss.FactPatientBillingLineItemDetail.pblid"); + public static readonly StrategicOpportunitySourceDbTable FactPatientCostDetail = new ("dss.factpatientcostdetail.costdetail"); + public static readonly StrategicOpportunitySourceDbTable DimChargeCode = new ("fw.dimChargeCode.cc"); + public static readonly StrategicOpportunitySourceDbTable DimPatientType = new ("fw.dimPatientType.PATIENTTYPE"); + public static readonly StrategicOpportunitySourceDbTable DimPhysician = new ("dss.dimPhysician.physPrimary"); + public static readonly StrategicOpportunitySourceDbTable FactPatientEncounterCLinicalIndicator = new ("dss.factPatientEncounterCLinicalIndicator.dsspeci"); + public static readonly StrategicOpportunitySourceDbTable FactExplorationPopulationEncounter = new ("cci.factExplorationPopulationEncounter.explorationPopulationEncounter"); + public static readonly StrategicOpportunitySourceDbTable DimMsdrg = new ("dss.dimmsdrg.msdrg"); + + public string Value { get; private set; } + + public string SqlAlias => Value.Split('.').Last(); + + public static IEnumerable GetAllValues() + { + return new List + { + FactPatientEncounterSummary, + FactPatientBillingLineItemDetail, + FactPatientCostDetail, + DimChargeCode, + DimPatientType, + DimPhysician, + FactPatientEncounterCLinicalIndicator, + FactExplorationPopulationEncounter, + DimMsdrg + }; + } + + public override string ToString() + { + return Value; + } + + private StrategicOpportunitySourceDbTable(string value) + { + Value = value; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityUpdateStatusDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityUpdateStatusDto.cs new file mode 100644 index 0000000..8893e87 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityUpdateStatusDto.cs @@ -0,0 +1,11 @@ +using System; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityUpdateStatusDto + { + public byte Status { get; set; } + public DateOnly TrackingStartDate { get; set; } + public DateOnly TrackingEndDate { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityWorkbookDto.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityWorkbookDto.cs new file mode 100644 index 0000000..85f04db --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicOpportunityWorkbookDto.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicOpportunityWorkbookDto + { + public long OpportunityId { get; set; } + public int WorkbookId { get; set; } + public string Name { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicRolesUpdate.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicRolesUpdate.cs new file mode 100644 index 0000000..032faab --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Dtos/StrategicRolesUpdate.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos +{ + public class StrategicRolesUpdate + { + public Guid OwnerGuid { get; set; } + public Guid ExecutiveSponsorGuid { get; set; } + public Guid CostLeaderGuid { get; set; } + public string TeamMembers { get; set; } + public IEnumerable Stakeholders { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Email/IPrimaryMeasureMonitoringEmail.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Email/IPrimaryMeasureMonitoringEmail.cs new file mode 100644 index 0000000..df8af48 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Email/IPrimaryMeasureMonitoringEmail.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Email +{ + public interface IPrimaryMeasureMonitoringEmail + { + Task SendEmail(Guid receiver, List tableData, string appVersion, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Email/PrimaryMeasureMonitoringData.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Email/PrimaryMeasureMonitoringData.cs new file mode 100644 index 0000000..7a17c14 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Email/PrimaryMeasureMonitoringData.cs @@ -0,0 +1,12 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Email +{ + public class PrimaryMeasureMonitoringData + { + public long OpportunityId { get; set; } + public string Name { get; set; } + public double PeriodAverage { get; set; } + public double MonthAverage { get; set; } + public double Variance { get; set; } + public bool IsCurrencyMeasure { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Email/PrimaryMeasureMonitoringEmail.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Email/PrimaryMeasureMonitoringEmail.cs new file mode 100644 index 0000000..4847766 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Email/PrimaryMeasureMonitoringEmail.cs @@ -0,0 +1,67 @@ +using Microsoft.Extensions.Configuration; +using Strata.ContinuousImprovement.Biz.Email; +using Strata.Users.Client; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Email +{ + public class PrimaryMeasureMonitoringEmail : IPrimaryMeasureMonitoringEmail + { + private readonly IEmailNotificationService _emailNotificationService; + private readonly IConfiguration _config; + private readonly IUsersServiceClient _usersServiceClient; + + public PrimaryMeasureMonitoringEmail(IEmailNotificationService emailNotificationService, IConfiguration config, IUsersServiceClient usersServiceClient) + { + _emailNotificationService = emailNotificationService; + _usersServiceClient = usersServiceClient; + _config = config; + } + public async Task SendEmail(Guid receiver, List tableData, string appVersion, CancellationToken cancellationToken) + { + var user = await _usersServiceClient.GetUserAsync(receiver, cancellationToken); + var result = await _emailNotificationService.SendEmailAsync(user.Email, new EmailInfo() + { + Subject = "Strategic Tracker primary measures have exceeded the defined threshold", + Body = GenerateEmailBody(tableData, appVersion) + }); + + return result; + } + + private string GenerateEmailBody(List tableData, string appVersion) + { + var templatePath = Path.Combine("StrategicOpportunities", "Email", "PrimaryMeasureMonitoringEmail.html"); + var baseBody = _emailNotificationService.BuildEmailBody(templatePath, appVersion); + var link = _config["microapps:continuousImprovement"] + "strategic-opportunity"; + var details = new StringBuilder(); + + foreach (var row in tableData) + { + var encodedName = WebUtility.HtmlEncode(row.Name); + details.Append(@$" + + {row.OpportunityId} +

{encodedName}

+ {(row.IsCurrencyMeasure ? "$" : "")}{row.PeriodAverage.ToString("N0")} + {(row.IsCurrencyMeasure ? "$" : "")}{row.MonthAverage.ToString("N0")} + {row.Variance.ToString("F1")}% + +"); + } + + var previousMonthDate = DateTime.UtcNow.AddMonths(-1); + baseBody = baseBody.Replace("[STRATA_MONTH_YEAR]", previousMonthDate.ToString("MMMM, yyyy")); + baseBody = baseBody.Replace("[STRATA_REQUEST_BODY]", details.ToString()); + baseBody = baseBody.Replace("[STRATA_INITIATIVE_COUNT]", tableData.Count.ToString()); + + return baseBody; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/BaselineType.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/BaselineType.cs new file mode 100644 index 0000000..2fa546a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/BaselineType.cs @@ -0,0 +1,10 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums +{ + public enum BaselineType: byte + { + MonthRange = 1, + FlatBudget, + Custom, + Override + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/DesiredResult.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/DesiredResult.cs new file mode 100644 index 0000000..c7a2018 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/DesiredResult.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums +{ + public enum DesiredResult : byte + { + Increase = 1, + Decrease, + Steady + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/DistributionMethod.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/DistributionMethod.cs new file mode 100644 index 0000000..50198b0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/DistributionMethod.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums +{ + public enum DistributionMethod : byte + { + Average = 1, + Monthly + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/DistributionStage.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/DistributionStage.cs new file mode 100644 index 0000000..f5a7fe1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/DistributionStage.cs @@ -0,0 +1,11 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums +{ + public enum DistributionStage : byte + { + FromSourceToOpportunity = 1, + OpportunityChangeGoalPeriod, + FromOpportunityToInitiative, + InitiativeChangeTrackingPeriod, + FromInitiativeToOpportunity + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/GLAccountType.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/GLAccountType.cs new file mode 100644 index 0000000..756662b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/GLAccountType.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums +{ + public enum GLAccountType : byte + { + Revenue = 0, + Expense = 1, + Margin = 2 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/MeasureGroup.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/MeasureGroup.cs new file mode 100644 index 0000000..a48cb3c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/MeasureGroup.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums +{ + public enum MeasureGroup : byte + { + System = 0, + Primary, + Secondary + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/MeasureType.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/MeasureType.cs new file mode 100644 index 0000000..473594a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/MeasureType.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums +{ + public enum MeasureType : byte + { + UnitsOfService = 1, + UnitsOfServicePerCase, + DirectVariableCost, + DirectVariableCostPerCase, + DirectVariableCostPerUnit, + TotalDirectCost, + TotalDirectCostPerCase, + TotalDirectCostPerUnit, + TotalCharges, + TotalChargesPerCase, + TotalChargesPerUnit, + EncountersCases, + AcquisitionSupplyCost, + AcquisitionSupplyCostPerCase, + AcquisitionSupplyCostPerUnit, + AverageLengthOfStay, + Days, + TotalMargin, + MarginPerCase, + ContributionMargin, + ContributionMarginPerCase, + DirectMargin, + DirectMarginPerCase, + VariableMargin, + VariableMarginPerCase, + + CommittedCost, + ActualCost, + + // GL measure types + GLExpense, + GLRevenue, + GLVolume, + GLExpensePerUnit, + GLRevenuePerUnit, + GLExpensePerAdjDischarge, + GLRevenuePerAdjDischarge, + GLMargin, + GLMarginPerUnit, + GLMarginPerAdjDischarge + } + + public static class MeasureTypeConstants + { + public readonly static ImmutableList PER_UNIT_MEASURE_TYPES = ImmutableList.Create( + MeasureType.DirectVariableCostPerUnit, + MeasureType.TotalDirectCostPerUnit, + MeasureType.TotalChargesPerUnit, + MeasureType.AcquisitionSupplyCostPerUnit); + public readonly static ImmutableList PER_CASE_MEASURE_TYPES = ImmutableList.Create( + MeasureType.DirectVariableCostPerCase, + MeasureType.TotalDirectCostPerCase, + MeasureType.TotalChargesPerCase, + MeasureType.AcquisitionSupplyCostPerCase, + MeasureType.UnitsOfServicePerCase, + MeasureType.EncountersCases, + MeasureType.AverageLengthOfStay, + MeasureType.MarginPerCase, + MeasureType.ContributionMarginPerCase, + MeasureType.DirectMarginPerCase, + MeasureType.VariableMarginPerCase); + public readonly static ImmutableList PATIENT_COST_MEASURE_TYPES = ImmutableList.Create( + MeasureType.DirectVariableCost, + MeasureType.DirectVariableCostPerCase, + MeasureType.DirectVariableCostPerUnit, + MeasureType.TotalDirectCost, + MeasureType.TotalDirectCostPerCase, + MeasureType.TotalDirectCostPerUnit ); + public readonly static ImmutableList MARGIN_MEASURE_TYPES = ImmutableList.Create( + MeasureType.TotalMargin, + MeasureType.MarginPerCase, + MeasureType.ContributionMargin, + MeasureType.ContributionMarginPerCase, + MeasureType.DirectMargin, + MeasureType.DirectMarginPerCase, + MeasureType.VariableMargin, + MeasureType.VariableMarginPerCase ); + public readonly static ImmutableList GL_MEASURE_TYPES = ImmutableList.Create( + MeasureType.GLExpense, + MeasureType.GLRevenue, + MeasureType.GLVolume, + MeasureType.GLExpensePerUnit, + MeasureType.GLRevenuePerUnit, + MeasureType.GLExpensePerAdjDischarge, + MeasureType.GLRevenuePerAdjDischarge, + MeasureType.GLMargin, + MeasureType.GLMarginPerUnit, + MeasureType.GLMarginPerAdjDischarge); + public static bool IsEncounterLevelMeasure(MeasureType type) + { + return MARGIN_MEASURE_TYPES.Contains(type) || type == MeasureType.EncountersCases || type == MeasureType.AverageLengthOfStay; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/RunTrackingStatus.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/RunTrackingStatus.cs new file mode 100644 index 0000000..5b7fb30 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/RunTrackingStatus.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums +{ + public enum RunTrackingStatus : byte + { + None, + Success, + Failed + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/StrategicOpportunityStatus.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/StrategicOpportunityStatus.cs new file mode 100644 index 0000000..73976cf --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Enums/StrategicOpportunityStatus.cs @@ -0,0 +1,13 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums +{ + public enum StrategicOpportunityStatus + { + NewOpportunity, + InValidation, + PendingApproval, + NewInitiative, + Tracking, + Complete, + CompleteMonitoring + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/GLDataSourceStrategy.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/GLDataSourceStrategy.cs new file mode 100644 index 0000000..a28da2f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/GLDataSourceStrategy.cs @@ -0,0 +1,32 @@ +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class GLDataSourceStrategy(ISnowflakeDatabaseContext snowflakeContext) : IGLDataSourceStrategy + { + private const string FwGLTable = "fw.factglsampled"; + private const string IntGLTable = "int.factgl"; + private const string FwStatisticTable = "fw.factstatisticsampledmonthly"; + private const string IntStatisticTable = "int.FactStatistic"; + + public async Task GetGLFactTableAsync() + { + var hasData = await HasAnyDataAsync(FwGLTable); + return hasData ? FwGLTable : IntGLTable; + } + + public async Task GetStatisticTableAsync() + { + var hasData = await HasAnyDataAsync(FwStatisticTable); + return hasData ? FwStatisticTable : IntStatisticTable; + } + + private async Task HasAnyDataAsync(string tableName) + { + var result = await snowflakeContext.ExecuteScalarAsync( + $"SELECT CASE WHEN EXISTS (SELECT 1 FROM {tableName} LIMIT 1) THEN 1 ELSE 0 END"); + return result == 1; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IGLDataSourceStrategy.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IGLDataSourceStrategy.cs new file mode 100644 index 0000000..5926160 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IGLDataSourceStrategy.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public interface IGLDataSourceStrategy + { + Task GetGLFactTableAsync(); + Task GetStatisticTableAsync(); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicChargeCodeOpportunityDetailService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicChargeCodeOpportunityDetailService.cs new file mode 100644 index 0000000..45dae44 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicChargeCodeOpportunityDetailService.cs @@ -0,0 +1,26 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public interface IStrategicChargeCodeOpportunityDetailService + { + Task GetStrategicOpportunityDetailData(long opportunityId, bool isTracking, StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken); + Task PopulateTrackingDetails(StrategicOpportunityChargeCode opportunity, CancellationToken cancellationToken); + Task UpdateMeasuresDetails(StrategicOpportunityChargeCode opportunity, CancellationToken cancellationToken); + Task UpdateMeasuresAverage(StrategicOpportunityChargeCode opportunity, CancellationToken cancellationToken); + Task DeleteMeasuresDetails(long opportunityId, CancellationToken cancellationToken); + Task PopulateStrategicOpportunityDetails(StrategicOpportunityChargeCode opportunity, CancellationToken cancellationToken); + Task PopulateStrategicOpportunityGoalDetails(StrategicOpportunityChargeCode opportunity, DistributionStage stage, bool isSaveChanges, CancellationToken cancellationToken); + Task PopulateStrategicOpportunityCommittedDetails(StrategicOpportunityChargeCode opportunity, DistributionStage stage, bool isSaveChanges, CancellationToken cancellationToken); + Task PopulateDetailGoalSummary(StrategicOpportunityChargeCode opportunity, double[] rampUps, bool isSaveChanges, CancellationToken cancellationToken); + Task UpdateDetailCommittedByRampUp(StrategicOpportunityChargeCode opportunity, double[] oldRampUps, double[] newRampUps, bool isSaveChanges, CancellationToken cancellationToken); + Task UpdateStrategicOpportunityDetailsData(long opportunityId, IEnumerable adjustments, IEnumerable rows, CancellationToken cancellationToken); + Task ExportInitiativeAsync(long opportunityId, bool isTracking, StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken); + Task ExportOpportunityAsync(long opportunityId, StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicGLOpportunityDetailService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicGLOpportunityDetailService.cs new file mode 100644 index 0000000..b71dd8a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicGLOpportunityDetailService.cs @@ -0,0 +1,20 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public interface IStrategicGLOpportunityDetailService + { + Task GetStrategicOpportunityGLDetailData(long opportunityId, bool isTracking, string[] dimensions, CancellationToken cancellationToken); + Task PopulateStrategicOpportunityGLDetails(StrategicOpportunityGL opportunity, CancellationToken cancellationToken); + Task PopulateStrategicOpportunityGLGoalDetails(StrategicOpportunityGL opportunity, DistributionStage stage, bool isSaveChanges, CancellationToken cancellationToken); + Task PopulateStrategicOpportunityCommittedDetails(StrategicOpportunityGL opportunity, DistributionStage stage, bool isSaveChanges, CancellationToken cancellationToken); + Task UpdateDetailCommittedByRampUp(StrategicOpportunityGL opportunity, double[] oldRampUps, double[] newRampUps, bool isSaveChanges, CancellationToken cancellationToken); + Task PopulateGLDetailGoalSummary(StrategicOpportunityGL opportunity, double[] rampUps, bool isSaveChanges, CancellationToken cancellationToken); + Task UpdateStrategicOpportunityGLDetailsData(long opportunityId, IEnumerable adjustments, IEnumerable rows, CancellationToken cancellationToken); + Task PopulateTrackingDetails(StrategicOpportunityGL opportunity, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicItemDimensionSyncService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicItemDimensionSyncService.cs new file mode 100644 index 0000000..953e79a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicItemDimensionSyncService.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public interface IStrategicItemDimensionSyncService + { + Task UpsertAsync(StrategicOpportunity opportunity, CancellationToken cancellationToken); + Task DeleteAsync(long opportunityId); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicOpportunityService.cs new file mode 100644 index 0000000..ed7fb16 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/IStrategicOpportunityService.cs @@ -0,0 +1,40 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public interface IStrategicOpportunityService + { + Task> GetStrategicOpportunitiesAsync(CancellationToken cancellationToken); + Task GetStrategicOpportunityGeneralInfo(long opportunityId, CancellationToken cancellationToken); + Task GetStrategicOpportunityChargeCode(long opportunityId, CancellationToken cancellationToken); + Task ContainsAsync(long opportunityId, CancellationToken cancellationToken); + Task ContainsGLAsync(long opportunityId, CancellationToken cancellationToken); + Task CreateOpportunityChargeCodeAsync(StrategicOpportunityChargeCodeDto opportunityDto, CancellationToken cancellationToken); + Task UpdateOpportunityChargeCode(long opportunityId, StrategicOpportunityChargeCodeUpdateDto opportunityDto, CancellationToken cancellationToken); + Task CreateOpportunityGeneralLedgerAsync(StrategicOpportunityGLDto opportunityDto, CancellationToken cancellationToken); + Task UpdateOpportunityGeneralLedger(long opportunityId, StrategicOpportunityGLUpdateDto opportunityDto, CancellationToken cancellationToken); + Task GetStrategicOpportunityGL(long opportunityId, CancellationToken cancellationToken); + Task DeleteGeneralLedgerOpportunityAsync(long opportunityId, CancellationToken cancellationToken); + Task GetStrategicOpportunityLayout(long opportunityId, CancellationToken cancellationToken); + Task UpdateStrategicOpportunityLayout(long opportunityId, StrategicOpportunityLayout layout, CancellationToken cancellationToken); + Task UpdateStrategicOpportunityStatus(long opportunityId, StrategicOpportunityUpdateStatusDto updateStatusDto, CancellationToken cancellationToken); + Task DeleteChargeCodeOpportunityAsync(long opportunityId, CancellationToken cancellationToken); + Task NameIsValidAsync(string name, CancellationToken cancellationToken); + Task UpdateStrategicOpportunityRampUpsAsync(long opportunityId, List rampUps, CancellationToken cancellationToken); + Task ResetStrategicOpportunityRampUps(long opportunityId, CancellationToken cancellationToken); + Task RunTrackingTask(long opportunityId, CancellationToken cancellationToken); + Task CompleteInitiative(long opportunityId, StrategicOpportunityCompleteInitiativeData completeInitiativeData, CancellationToken cancellationToken); + Task> GetStrategicOpportunityMeasures(long opportunityId, CancellationToken cancellationToken); + Task GetStrategicOpportunityPrimaryMeasure(long opportunityId, CancellationToken cancellationToken); + Task> GetStrategicOpportunityMeasureDetails(long measureId, CancellationToken cancellationToken); + Task> GetStrategicOpportunityRampUpsAsync(long opportunityId, CancellationToken cancellationToken); + Task GetStrategicOpportunityDrawerDetails(long opportunityId, CancellationToken cancellationToken); + Task> GetStrategicInitiativesAsync(CancellationToken cancellationToken); + Task UpdateStrategicInitiative(long opportunityId, StrategicInitiativeUpdateDto updateInitiativeDto, CancellationToken cancellationToken); + Task UpdateRoles(long opportunityId, StrategicRolesUpdate updateStakeholdersDto, CancellationToken cancellationToken); + Task CopyStrategicOpportunity(long opportunityId, string opportunityName, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/PrimaryMeasureMonitoringJob.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/PrimaryMeasureMonitoringJob.cs new file mode 100644 index 0000000..d84f004 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/PrimaryMeasureMonitoringJob.cs @@ -0,0 +1,112 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.ClientLicense; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Email; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class PrimaryMeasureMonitoringJob(CentralDbContext centralDbContext, + ISnowflakeDatabaseContextFactory snowflakeDatabaseContextFactory, + IPrimaryMeasureMonitoringEmail primaryMeasureMonitoringEmail, + IBackgroundJobClient backgroundJobClient, + IClientLicenseService clientLicenseService, + ILogger logger = default) + { + private readonly List _numberMeasureTypes = new List { MeasureType.UnitsOfService, MeasureType.UnitsOfServicePerCase, MeasureType.EncountersCases, MeasureType.AverageLengthOfStay, MeasureType.Days }; + + [DeleteOnSuccess] + [JobDisplayName("Run email sending task about primary measure monitoring")] + [Semaphore(nameof(PrimaryMeasureMonitoringJob.RunNotifyPrimaryMeasureMonitoring))] + public async Task RunNotifyPrimaryMeasureMonitoring(CancellationToken cancellationToken) + { + var clients = await clientLicenseService.GetEligibleCIClientsAsync(isLicensed: true, cancellationToken); + + foreach (var client in clients) + { + var measures = await centralDbContext.StrategicOpportunityMeasures.Include(m => m.Opportunity).Where(m => m.StrataId == client.StrataId && m.MeasureGroup == MeasureGroup.Primary && m.Opportunity.Status == (byte)StrategicOpportunityStatus.CompleteMonitoring).ToListAsync(cancellationToken); + + if (measures.Any()) + { + backgroundJobClient.Enqueue(job => job.SendEmail(client.DatabaseGUID, client.StrataId, client.AppVersion, cancellationToken)); + } + } + } + + [DeleteOnSuccess] + [JobDisplayName("Send Primary Measure Monitoring Email ({1})")] + [CaptureJobParameter(DatabaseGuidArgumentPosition = 0, StrataIdArgumentPosition = 1)] + public async Task SendEmail(Guid databaseGuid, int strataId, string appVersion, CancellationToken cancellationToken) + { + try + { + using var snowflakeContext = snowflakeDatabaseContextFactory.Create(databaseGuid.ToString("N")); + var measures = await centralDbContext.StrategicOpportunityMeasures.Include(m => m.Opportunity).Where(m => m.StrataId == strataId && m.MeasureGroup == MeasureGroup.Primary && m.Opportunity.Status == (byte)StrategicOpportunityStatus.CompleteMonitoring).ToListAsync(cancellationToken); + var previousMonthDate = DateTime.UtcNow.AddMonths(-1); + var measureIds = measures.Select(m => m.MeasureId).Distinct().ToList(); + var measureIdsInClause = string.Join(",", measureIds); + var sql = @$"SELECT MeasureId, Value FROM cci.StrategicOpportunityMeasureDetail + WHERE YearId={previousMonthDate.Year} and MonthId={previousMonthDate.Month} and MeasureId IN ({measureIdsInClause})"; + var measureDetails = await snowflakeContext.QueryAsync(sql, "MeasureDetails"); + var notifyMeasures = new Dictionary(); + + foreach (var measure in measures) + { + var detail = measureDetails.FirstOrDefault(m => m.MeasureId == measure.MeasureId); + + if (detail != null && (detail.Value < measure.AverageValue * (1 - measure.AcceptableThreshold) || detail.Value > measure.AverageValue * (1 + measure.AcceptableThreshold))) + { + notifyMeasures.Add(measure, detail.Value); + } + } + + var userGuids = notifyMeasures + .SelectMany(m => new[] { m.Key.Opportunity.OwnerGuid, m.Key.Opportunity.ExecutiveSponsorGuid }) + .Where(guid => guid != Guid.Empty) + .Distinct() + .ToList(); + var sendEmails = new Dictionary>(); + + foreach (var userGuid in userGuids) + { + var sendMeasures = notifyMeasures.Where(m => m.Key.Opportunity.OwnerGuid == userGuid || m.Key.Opportunity.ExecutiveSponsorGuid == userGuid) + .Select(m => + { + return new PrimaryMeasureMonitoringData() + { + OpportunityId = m.Key.OpportunityId, + Name = m.Key.Opportunity.Name, + PeriodAverage = m.Key.AverageValue, + MonthAverage = m.Value, + IsCurrencyMeasure = !_numberMeasureTypes.Contains(m.Key.MeasureType), + Variance = m.Key.AverageValue == 0 ? 0 : m.Value / m.Key.AverageValue * 100 + }; + }) + .ToList(); + + sendEmails.Add(userGuid, sendMeasures); + } + + string version = appVersion.Replace(".", ""); + var sendEmailTasks = sendEmails.Select(email => primaryMeasureMonitoringEmail.SendEmail(email.Key, email.Value, version, cancellationToken)); + await Task.WhenAll(sendEmailTasks); + } + catch (Exception ex) + { + logger.LogServiceError(ex, $"Primary measure monitoring email failed for strataId: {strataId}"); + throw; + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/IStrategicOpportunityDetailQueryBuilder.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/IStrategicOpportunityDetailQueryBuilder.cs new file mode 100644 index 0000000..d54afca --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/IStrategicOpportunityDetailQueryBuilder.cs @@ -0,0 +1,20 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.DataSchema.Models.Query; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries +{ + public interface IStrategicOpportunityDetailQueryBuilder + { + string BuildDeleteQuery(long? opportunityId = null); + string BuildInsertQuery(StrategicOpportunityChargeCode opportunity, string selectQuery, bool isUsingAprdrgAsync); + string BuildMergeQuery(long opportunityId, string sqlQuery, bool isUsingAprdrg); + string GetDeleteAllMeasuresSql(IEnumerable measureIds); + string GetSelectMeasureSql(long opportunityId, MeasureType measureType, string sqlQuery, Dictionary marginMeasureFilters); + string BuildMergeSystemMeasureMergeQuery(long opportunityId, long measureId, MeasureType measureType, string startDate, int trackingMonths); + string WrapIntoMeasureAverageQuery(string query); + string WrapIntoMeasureMergeQuery(string query); + string BuildGetDistinctEncounterCountQuery(long opportunityId, string selectQuery, bool isUsingAprdrg, StrategicOpportunityGetDetailData getData); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/QUERY_BUILDERS.md b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/QUERY_BUILDERS.md new file mode 100644 index 0000000..ecd2597 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/QUERY_BUILDERS.md @@ -0,0 +1,153 @@ +# Strategic Opportunity Query Builders + +## Overview + +This folder contains dynamic SQL builders that generate Snowflake SQL at runtime. They are not +ORM queries — they produce raw SQL strings that are executed via `ISnowflakeDatabaseContext`. + +All Snowflake tables targeted here are in the `cci` schema. + +--- + +## Files + +| File | Builds SQL for | +|---|---| +| `StrategicOpportunityDetailQueryBuilder.cs` | Charge code detail: INSERT, MERGE, DELETE, encounter count, measure merge | +| `StrategicOpportunityUpdateQueryBuilder.cs` | Manual adjustment UPDATEs on `FactStrategicChargeCodeDetail` | +| `StrategicOpportunityGLDetailQueryBuilder.cs` | GL detail: equivalent INSERT/MERGE/DELETE for `FactStrategicGLDetail` | +| `StrategicOpportunityGLUpdateQueryBuilder.cs` | Manual adjustment UPDATEs on `FactStrategicGLDetail` | +| `StrategicOpportunityServiceQuery.cs` | Dimension key-value lookup queries (returns `Dictionary`) | +| `StrategicOpportunityGLServiceQuery.cs` | GL dimension key-value lookups | +| `SQL/` | Embedded SQL resource files loaded via `Assembly.ReadResource()` | + +--- + +## StrategicOpportunityDetailQueryBuilder + +### Charge Code Injection Pattern + +The core technique is **anchor-based injection**. Source SELECT queries (loaded from `.sql` files +in `/Queries/SQL/`) contain a known anchor string: + +```sql +INNER JOIN FW.DIMCHARGECODE AS CC ON CC.chargecodeid = PBLID.chargecodeid +``` + +`InjectChargeCodeJoin()` finds every occurrence of this anchor and inserts after it: + +```sql +INNER JOIN cci.StrategicChargeCodeIds_{opportunityId} CCID ON CC.chargecodeid = CCID.chargecodeid +``` + +`cci.StrategicChargeCodeIds_{opportunityId}` is a per-opportunity temp table that filters the +query to only the charge codes selected for this opportunity. The table name is dynamic — it +encodes the `opportunityId` directly in the name. + +### KEY_SEPARATOR + +```csharp +private const string KEY_SEPARATOR = "|"; +``` + +Used in encounter-count queries to build composite row keys: + +```sql +ARRAY_TO_STRING(ARRAY_CONSTRUCT_COMPACT(dim1, dim2, ...), '|') AS key +``` + +The frontend `StrategicOpportunityDetailStoreTreeData` uses the same `|` separator to look up +encounter counts by node key when populating the `encounters` field. + +### BuildInsertQuery vs BuildMergeQuery + +| Method | When used | Behavior | +|---|---|---| +| `BuildInsertQuery` | Initial baseline population | Plain `INSERT INTO cci.FactStrategicChargeCodeDetail` | +| `BuildMergeQuery` | Re-population / refresh | `MERGE` matching on all 10 dimension columns — updates `Units/Cost`, inserts if missing | +| `BuildDeleteQuery` | Before full re-population or on delete | `DELETE WHERE OpportunityId = X` | + +The MERGE match key is all 10 dimension columns: +`OpportunityId, IsBaseline, FiscalYear, FiscalMonth, EntityId, PhysicianId, ServiceLineId, DepartmentId, PhysicianSpecialtyId, ChargeCodeId, CostDriver, CaseTypeFamilyId` + +### GetSubQuery — Aggregation Wrapper + +Every INSERT/MERGE wraps the source SELECT in a `GetSubQuery()` that: +1. Maps Snowflake column aliases (e.g. `dsspesEntityID` → `EntityID`) +2. Adds `:opportunityId` and `:isBaseline` bind parameters +3. Groups by all 10 dimension columns, summing `Units` and `Cost` +4. Sets `GoalUnits = 0, GoalCost = 0` (goal values are populated separately via distribution) + +### CaseTypeFamilyId — APR-DRG vs MS-DRG + +```csharp +GetCaseTypeFamilyIdField(bool isUsingAprdrg) +``` + +For inpatient encounters, `CaseTypeFamilyId` is derived from either `APRDRGCaseTypeFamilyId` +or `MSDRGCaseTypeFamilyId` depending on the `isUsingAprdrg` client setting. Outpatient always +uses `CPTCodeCaseTypeFamilyId`. This flag is passed through from the opportunity's client +configuration. + +### Measure Merge Queries + +`BuildMeasureMergeQuery` targets `cci.StrategicOpportunityMeasureDetail` (not the fact table). +It MERGEs on `(MeasureId, YearId, MonthId)`. + +`BuildSystemMeasureMergeQuery` handles system-calculated measures (CommittedCost, Cost) by +reading directly from `cci.FactStrategicChargeCodeDetail` with a date-range generator — no source +query injection needed. + +`BuildMeasureCalculation()` maps `MeasureType` → the appropriate Snowflake aggregate expression +(SUM, COUNT DISTINCT, per-case division, margin formula, etc.). + +--- + +## Dimension Config — DimensionsConfig vs GLDimensionsConfig + +Defined as static dictionaries on `StrategicOpportunityService`: + +### Charge Code Dimensions (8) + +| Dimension | SQL File | Column (Snowflake) | Alias (source query) | +|---|---|---|---| +| Entity | `SelectEntityKeyValue.sql` | `EntityID` | `dsspesEntityID` | +| PrimaryServiceLine | `SelectServiceLineKeyValue.sql` | `PrimaryServiceLineID` | `dsspesPrimaryServiceLineId` | +| CaseTypeFamily | `SelectCaseTypeFamilyKeyValue.sql` | `CaseTypeFamilyId` | `CaseTypeFamilyId` | +| Physician | `SelectPhysicianKeyValue.sql` | `PrimaryPhysicianID` | `dsspesPhysicianId` | +| PhysicianSpecialty | `SelectPhysicianSpecialtyKeyValue.sql` | `PhysicianSpecialtyID` | `physPrimaryPhysicianSpecialtyId` | +| CostDriver | `SelectCostDriverKeyValue.sql` | `CostDriver` | `ccCostDriver` | +| Department | `SelectDepartmentKeyValue.sql` | `DepartmentID` | `pblidDepartmentId` | +| ChargeCode | `SelectChargeCodeKeyValue.sql` | `ChargeCodeID` | `ccChargeCodeID` | + +### GL Dimensions (3) + +| Dimension | SQL File | Column (Snowflake) | +|---|---|---| +| Entity | `SelectGLEntityKeyValue.sql` | `EntityId` | +| Department | `SelectDepartmentKeyValue.sql` | `DepartmentId` | +| GLAccount | `SelectGLAccountKeyValue.sql` | `AccountId` | + +Note: GL uses a separate Entity SQL file (`SelectGLEntityKeyValue.sql`) from charge code +(`SelectEntityKeyValue.sql`). + +--- + +## StrategicOpportunityServiceQuery / StrategicOpportunityGLServiceQuery + +These are not query *builders* — they are query *providers*. They return a dictionary mapping each +`StrategicOpportunityDetailDimension` to its lookup SQL. The calling service executes each query +against Snowflake and builds a `DimensionsData` map for the frontend tree store. + +--- + +## SQL Embedded Resources + +SQL files in `SQL/` are embedded assembly resources loaded via: + +```csharp +Assembly.GetExecutingAssembly().ReadResource("QueryName.sql") +``` + +See `Biz/Utilities/ResourceExtensions.cs` for the loader. The resource name must match exactly +(case-sensitive on Linux builds). diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectCaseTypeFamilyKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectCaseTypeFamilyKeyValue.sql new file mode 100644 index 0000000..1a1303b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectCaseTypeFamilyKeyValue.sql @@ -0,0 +1,7 @@ +-- {queryDetailList} + +SELECT DISTINCT + ctf.CaseTypeFamilyId as key, + ctf.Name as value +FROM DetailList dl +JOIN dss.CaseTypeFamily ctf ON ctf.CaseTypeFamilyId = dl.CaseTypeFamilyId; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectChargeCodeKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectChargeCodeKeyValue.sql new file mode 100644 index 0000000..3e32bfa --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectChargeCodeKeyValue.sql @@ -0,0 +1,7 @@ +-- {queryDetailList} + +SELECT DISTINCT + cc.ChargeCodeId as key, + cc.Name as value +FROM DetailList dl +JOIN fw.DimChargeCode cc ON cc.ChargeCodeId = dl.ChargeCodeId; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectCostDriverKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectCostDriverKeyValue.sql new file mode 100644 index 0000000..3f5f803 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectCostDriverKeyValue.sql @@ -0,0 +1,6 @@ +-- {queryDetailList} + +SELECT DISTINCT + dl.CostDriver as key, + dl.CostDriver as value +FROM DetailList dl; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectDepartmentKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectDepartmentKeyValue.sql new file mode 100644 index 0000000..658067c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectDepartmentKeyValue.sql @@ -0,0 +1,7 @@ +-- {queryDetailList} + +SELECT DISTINCT + dd.DepartmentId as key, + dd.Name as value +FROM DetailList dl +JOIN fw.DimDepartment dd ON dd.DepartmentId = dl.DepartmentId; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectEntityKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectEntityKeyValue.sql new file mode 100644 index 0000000..f209b04 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectEntityKeyValue.sql @@ -0,0 +1,7 @@ +-- {queryDetailList} + +SELECT DISTINCT + de.EntityId as key, + de.Name as value +FROM DetailList dl +JOIN clientdss.DimEntity de ON de.EntityId = dl.EntityId; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectGLAccountKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectGLAccountKeyValue.sql new file mode 100644 index 0000000..799b98f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectGLAccountKeyValue.sql @@ -0,0 +1,7 @@ +-- {queryDetailList} + +SELECT DISTINCT + da.AccountId as key, + da.Name as value +FROM DetailList dl +JOIN fw.DimAccount da ON da.AccountId = dl.AccountId; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectGLEntityKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectGLEntityKeyValue.sql new file mode 100644 index 0000000..f01b28d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectGLEntityKeyValue.sql @@ -0,0 +1,7 @@ +-- {queryDetailList} + +SELECT DISTINCT + de.EntityId as key, + de.Name as value +FROM DetailList dl +JOIN fw.DimEntity de ON de.EntityId = dl.EntityId; diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectGLRollupKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectGLRollupKeyValue.sql new file mode 100644 index 0000000..0e0d1cd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectGLRollupKeyValue.sql @@ -0,0 +1,6 @@ +-- {queryDetailList} + +SELECT DISTINCT + dl.GLRollup as key, + dl.GLRollup as value +FROM DetailList dl; diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectPhysicianKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectPhysicianKeyValue.sql new file mode 100644 index 0000000..ee2faa0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectPhysicianKeyValue.sql @@ -0,0 +1,7 @@ +-- {queryDetailList} + +SELECT DISTINCT + ph.PhysicianId as key, + ph.FullName as value +FROM DetailList dl +JOIN dss.DimPhysician ph ON ph.PhysicianId = dl.PrimaryPhysicianId; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectPhysicianSpecialtyKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectPhysicianSpecialtyKeyValue.sql new file mode 100644 index 0000000..002259f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectPhysicianSpecialtyKeyValue.sql @@ -0,0 +1,7 @@ +-- {queryDetailList} + +SELECT DISTINCT + phs.PhysicianSpecialtyId as key, + phs.Name as value +FROM DetailList dl +JOIN dss.DimPhysicianSpecialty phs ON phs.PhysicianSpecialtyId = dl.PhysicianSpecialtyId; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectServiceLineKeyValue.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectServiceLineKeyValue.sql new file mode 100644 index 0000000..56f4c98 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectServiceLineKeyValue.sql @@ -0,0 +1,7 @@ +-- {queryDetailList} + +SELECT DISTINCT + dsl.ServiceLineId as key, + dsl.Name as value +FROM DetailList dl +JOIN fw.DimServiceLine dsl ON dsl.ServiceLineId = dl.PrimaryServiceLineId; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectStrategicOpportunityDetail.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectStrategicOpportunityDetail.sql new file mode 100644 index 0000000..12876a9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectStrategicOpportunityDetail.sql @@ -0,0 +1,20 @@ +-- {queryDetailList} + +SELECT + RowID, + IsBaseline, + DischargeFiscalYearId, + DischargeFiscalMonthId, + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CostDriver, + CaseTypeFamilyId, + Units, + Cost, + GoalUnits, + GoalCost +FROM DetailList dl; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectStrategicOpportunityGLDetail.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectStrategicOpportunityGLDetail.sql new file mode 100644 index 0000000..2765baf --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/SelectStrategicOpportunityGLDetail.sql @@ -0,0 +1,22 @@ +-- {queryDetailList} + +SELECT + RowID, + IsBaseline, + FiscalYearId, + FiscalMonthId, + EntityID, + DepartmentID, + AccountId, + GLRollup, + AccountType, + Amount, + GoalAmount, + DistributedAmount, + SUM(CASE WHEN AccountType = 0 THEN Amount ELSE 0 END) OVER (PARTITION BY IsBaseline, FiscalYearId, FiscalMonthId, EntityID, DepartmentID) AS RevenueAmount, + SUM(CASE WHEN AccountType = 1 THEN Amount ELSE 0 END) OVER (PARTITION BY IsBaseline, FiscalYearId, FiscalMonthId, EntityID, DepartmentID) AS ExpenseAmount, + SUM(CASE WHEN AccountType = 2 THEN Amount ELSE 0 END) OVER (PARTITION BY IsBaseline, FiscalYearId, FiscalMonthId, EntityID, DepartmentID) AS StatisticAmount, + SUM(CASE WHEN AccountType = 0 THEN GoalAmount ELSE 0 END) OVER (PARTITION BY IsBaseline, FiscalYearId, FiscalMonthId, EntityID, DepartmentID) AS GoalRevenueAmount, + SUM(CASE WHEN AccountType = 1 THEN GoalAmount ELSE 0 END) OVER (PARTITION BY IsBaseline, FiscalYearId, FiscalMonthId, EntityID, DepartmentID) AS GoalExpenseAmount, + SUM(CASE WHEN AccountType = 2 THEN GoalAmount ELSE 0 END) OVER (PARTITION BY IsBaseline, FiscalYearId, FiscalMonthId, EntityID, DepartmentID) AS GoalStatisticAmount +FROM DetailList dl; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/StrategicOpportunityDetailCTE.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/StrategicOpportunityDetailCTE.sql new file mode 100644 index 0000000..5c8c18c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/StrategicOpportunityDetailCTE.sql @@ -0,0 +1,23 @@ + +WITH DetailList AS ( + SELECT + RowID, + IsBaseline, + DischargeFiscalYearId, + DischargeFiscalMonthId, + EntityID, + PrimaryPhysicianID, + PrimaryServiceLineID, + DepartmentID, + PhysicianSpecialtyID, + ChargeCodeID, + CostDriver, + CaseTypeFamilyId, + Units, + Cost, + GoalUnits, + GoalCost + FROM cci.FactStrategicChargeCodeDetail sod + WHERE sod.OpportunityId = @OpportunityId AND (@IsBaseline IS NULL OR IsBaseline = @IsBaseline) + -- {queryWhere} +) \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/StrategicOpportunityGLDetailCTE.sql b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/StrategicOpportunityGLDetailCTE.sql new file mode 100644 index 0000000..bba9e71 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/SQL/StrategicOpportunityGLDetailCTE.sql @@ -0,0 +1,20 @@ + +WITH DetailList AS ( + SELECT + sod.RowID, + sod.IsBaseline, + sod.FiscalYearId, + sod.FiscalMonthId, + sod.EntityID, + sod.DepartmentID, + sod.AccountId, + da.GLRollup, + sod.AccountType, + sod.Amount, + sod.GoalAmount, + sod.DistributedAmount + FROM cci.FactStrategicGLDetail sod + JOIN fw.DimAccount da ON da.AccountId = sod.AccountId + WHERE sod.OpportunityId = @OpportunityId AND (@IsBaseline IS NULL OR sod.IsBaseline = @IsBaseline) + -- {queryWhere} +) \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityDetailQueryBuilder.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityDetailQueryBuilder.cs new file mode 100644 index 0000000..44afb90 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityDetailQueryBuilder.cs @@ -0,0 +1,375 @@ +using Microsoft.IdentityModel.Tokens; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.DataSchema.Models.Query; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries +{ + internal class StrategicOpportunityDetailQueryBuilder : IStrategicOpportunityDetailQueryBuilder + { + private const string StrategicOpportunityMeasureTableName = "cci.StrategicOpportunityMeasureDetail"; + private const string KEY_SEPARATOR = "|"; + + public string BuildInsertQuery(StrategicOpportunityChargeCode opportunity, string selectQuery, bool isUsingAprdrg) + => InjectInsert(selectQuery, isUsingAprdrg, opportunity.OpportunityId); + + public string BuildDeleteQuery(long? opportunityId = null) + { + return $"DELETE FROM {StrategicOpportunityService.StrategicOpportunityDetailTableName} where OpportunityId = {(opportunityId.HasValue ? opportunityId.Value : ":opportunityId;")}"; + } + + public string BuildMergeQuery(long opportunityId, string selectQuery, bool isUsingAprdrg) + { + if (!selectQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Unexpected SQL syntax. SELECT should be first word."); + } + + selectQuery = InjectChargeCodeJoin(opportunityId, selectQuery); + + var sql = $@" +MERGE INTO {StrategicOpportunityService.StrategicOpportunityDetailTableName} t +USING ( +{GetSubQuery(selectQuery, isUsingAprdrg)} +) s +ON t.OpportunityId = s.""OpportunityId"" + AND t.IsBaseline = s.""IsBaseline"" + AND t.DischargeFiscalYearId = s.""DischargeFiscalYearId"" + AND t.DischargeFiscalMonthId = s.""DischargeFiscalMonthId"" + AND t.EntityId = s.""EntityID"" + AND t.PrimaryPhysicianId = s.""PrimaryPhysicianId"" + AND t.PrimaryServiceLineId = s.""PrimaryServiceLineId"" + AND t.DepartmentId = s.""DepartmentId"" + AND t.PhysicianSpecialtyId = s.""PhysicianSpecialtyId"" + AND t.ChargeCodeId = s.""ChargeCodeID"" + AND t.CostDriver = s.""CostDriver"" + AND t.CaseTypeFamilyId = s.""CaseTypeFamilyId"" +WHEN MATCHED THEN + UPDATE SET + t.Units = s.""Units"", + t.Cost = s.""Cost"" +WHEN NOT MATCHED THEN + INSERT ( + OpportunityId, + IsBaseline, + DischargeFiscalYearId, + DischargeFiscalMonthId, + EntityId, + PrimaryPhysicianId, + PrimaryServiceLineId, + DepartmentId, + PhysicianSpecialtyId, + ChargeCodeId, + CostDriver, + CaseTypeFamilyId, + Units, + Cost, + GoalUnits, + GoalCost + ) +VALUES ( +s.""OpportunityId"", + s.""IsBaseline"", + s.""DischargeFiscalYearId"", + s.""DischargeFiscalMonthId"", + s.""EntityID"", + s.""PrimaryPhysicianId"", + s.""PrimaryServiceLineId"", + s.""DepartmentId"", + s.""PhysicianSpecialtyId"", + s.""ChargeCodeID"", + s.""CostDriver"", + s.""CaseTypeFamilyId"", + s.""Units"", + s.""Cost"", + s.""GoalUnits"", + s.""GoalCost"" + )"; + return sql; + } + public string BuildGetDistinctEncounterCountQuery(long opportunityId, string selectQuery, bool isUsingAprdrg, StrategicOpportunityGetDetailData getData) + { + if (!selectQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Unexpected SQL syntax. SELECT should be first word."); + } + selectQuery = InjectChargeCodeJoin(opportunityId, selectQuery, null, null, "CC", "StrategicChargeCodeIds_CTE"); + + var dimensions = getData.Dimensions.Select(x => StrategicOpportunityDetailDimension.GetFromString(x)).ToList(); + var dimensionColumnNames = new List(); + foreach (var dimension in dimensions) + { + if (StrategicOpportunityService.DimensionsConfig.TryGetValue(dimension, out var dimensionConfig)) + { + dimensionColumnNames.Add($"\"{dimensionConfig.AliasColumnName}\""); + } + } + + var sqlDimensionColumnNames = string.Join(", ", dimensionColumnNames); + + var sql = $@" + SELECT + ARRAY_TO_STRING(ARRAY_CONSTRUCT_COMPACT({sqlDimensionColumnNames}), '{KEY_SEPARATOR}') AS key, + COUNT(DISTINCT ""dsspesEncounterId"") AS value + FROM ({GetSubQueryForDistinctEncounter(selectQuery, isUsingAprdrg)}) AS t + GROUP BY ROLLUP ({sqlDimensionColumnNames})"; + return sql; + } + + public string BuildMergeSystemMeasureMergeQuery(long opportunityId, long measureId, MeasureType measureType, string startDate, int trackingMonths) + { + var selectQuery = $@" +SELECT + {measureId} AS ""MeasureId"", + ZEROIFNULL(SUM(""Value"")) AS ""Value"", + m.MonthId AS ""MonthId"", + m.YearId AS ""YearId"" +FROM ( +SELECT {(measureType == MeasureType.CommittedCost ? "GoalCost" : "Cost")} AS ""Value"", + DischargeFiscalMonthId AS ""MonthId"", + DischargeFiscalYearId AS ""YearId"" + FROM cci.FactStrategicChargeCodeDetail + WHERE Isbaseline = FALSE AND Opportunityid = {opportunityId}) AS ccd +RIGHT JOIN (SELECT + MONTH(monthDate) AS MonthId, + YEAR(monthDate) AS YearId + FROM ( + SELECT + DATEADD('month', seq4(), '{startDate}') AS monthDate + FROM TABLE(GENERATOR(ROWCOUNT => {trackingMonths})) + )) M on m.MonthId = ccd.""MonthId"" and m.YearId = ccd.""YearId"" + GROUP BY m.MonthId, m.YearId +"; + + return WrapIntoMeasureMergeQuery(selectQuery); + } + + public string GetSelectMeasureSql(long opportunityId, MeasureType measureType, string selectQuery, Dictionary marginFilters) + { + string joinSql = null; + var anchorSql = "RIGHT JOIN clientdss.FactPatientEncounterSummary AS DSSPES ON DSSPES.encounterid = PBLID.encounterid"; + if (MeasureTypeConstants.IsEncounterLevelMeasure(measureType)) + { + var filtersClause = marginFilters.Select(f => $"det.{f.Key} {(f.Value.Operator == FilterOperator.In ? "IN" : "NOT IN")} ({string.Join(", ", f.Value.Values)})"); + joinSql = @$"INNER JOIN ( + SELECT DISTINCT encounterId FROM dss.FactPatientBillingLineItemDetail det + JOIN StrategicChargeCodeIds_CTE ccf ON det.ChargeCodeId = ccf.ChargeCodeId +{(filtersClause.Any() ? $"WHERE {string.Join(" AND ", filtersClause)} " : string.Empty)}) ENID ON DSSPES.EncounterId = ENID.EncounterId"; + } + if (measureType == MeasureType.EncountersCases) + { + anchorSql = "FROM clientdss.FactPatientEncounterSummary AS DSSPES"; + } + selectQuery = InjectChargeCodeJoin(opportunityId, selectQuery, joinSql, anchorSql, "PBLID", "StrategicChargeCodeIds_CTE"); + + return $@" +SELECT + :measureId AS ""MeasureId"", + m.YearId AS ""YearId"", + m.MonthId AS ""MonthId"", + COALESCE({BuildMeasureCalculation(measureType)}, 0) AS ""Value"" +FROM ( + {selectQuery} +) + RIGHT JOIN (SELECT + MONTH(monthDate) AS MonthId, + YEAR(monthDate) AS YearId + FROM ( + SELECT + DATEADD('month', seq4(), :startDate) AS monthDate + FROM TABLE(GENERATOR(ROWCOUNT => :monthsNumber)) + )) M on m.MonthId = MONTH(TO_DATE(""DischargeDateTime"")) and m.YearId = YEAR(TO_DATE(""DischargeDateTime"")) +GROUP BY + m.MonthId, + m.YearId"; + } + + public string WrapIntoMeasureMergeQuery(string selectQuery) + { + return $@" + MERGE INTO {StrategicOpportunityMeasureTableName} t + USING ({selectQuery}) s + ON t.MeasureId = s.""MeasureId"" + AND t.YearId = s.""YearId"" + AND t.MonthId = s.""MonthId"" + WHEN MATCHED THEN + UPDATE SET + t.Value = s.""Value"" + WHEN NOT MATCHED THEN + INSERT (MeasureId, YearId, MonthId, Value) + VALUES (s.""MeasureId"", s.""YearId"", s.""MonthId"", s.""Value"") + "; + } + + public string WrapIntoMeasureAverageQuery(string selectQuery) + { + return $@"SELECT AVG(""Value"") FROM ( {selectQuery} )"; + } + + public string GetDeleteAllMeasuresSql(IEnumerable measureIds) + { + return measureIds != null && measureIds.Any() ? $@" + DELETE FROM {StrategicOpportunityMeasureTableName} AS td + WHERE td.MeasureId IN ({string.Join(", ", measureIds)}) + " : string.Empty; + } + + private static string BuildMeasureCalculation(MeasureType measureType) + { + return measureType switch + { + MeasureType.UnitsOfService or MeasureType.DirectVariableCost or MeasureType.TotalDirectCost + or MeasureType.TotalCharges or MeasureType.AcquisitionSupplyCost or MeasureType.Days => @"SUM(""Value"")", + MeasureType.UnitsOfServicePerCase or MeasureType.DirectVariableCostPerCase or MeasureType.TotalDirectCostPerCase + or MeasureType.TotalChargesPerCase or MeasureType.AcquisitionSupplyCostPerCase + or MeasureType.AverageLengthOfStay => @"SUM(""Value"") / COUNT(DISTINCT ""dsspesEncounterId"")", + MeasureType.DirectVariableCostPerUnit or MeasureType.TotalDirectCostPerUnit + or MeasureType.TotalChargesPerUnit or MeasureType.AcquisitionSupplyCostPerUnit => @"SUM(""Value"") / SUM(""UnitsOfService"")", + MeasureType.EncountersCases => @"COUNT(DISTINCT ""dsspesEncounterId"")", + MeasureType.TotalMargin or MeasureType.ContributionMargin + or MeasureType.DirectMargin or MeasureType.VariableMargin => @"SUM(""dsspesNetRevenue"" - ""Value"")", + MeasureType.MarginPerCase or MeasureType.ContributionMarginPerCase or MeasureType.DirectMarginPerCase + or MeasureType.VariableMarginPerCase => @"SUM(""dsspesNetRevenue"" - ""Value"") / COUNT(DISTINCT ""dsspesEncounterId"")", + _ => null + }; + } + + private static string InjectInsert(string selectQuery, bool isUsingAprdrg, long opportunityId) + { + // replace 'SELECT' with 'SELECT :opportunityId' + if (!selectQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Unexpected SQL syntax. SELECT should be first word."); + } + + selectQuery = InjectChargeCodeJoin(opportunityId, selectQuery); + + var sql = $@" +INSERT INTO {StrategicOpportunityService.StrategicOpportunityDetailTableName} ( + OpportunityId, + IsBaseline, + DischargeFiscalYearId, + DischargeFiscalMonthId, + EntityId, + PrimaryPhysicianId, + PrimaryServiceLineId, + DepartmentId, + PhysicianSpecialtyId, + ChargeCodeId, + CostDriver, + CaseTypeFamilyId, + Units, + Cost, + GoalUnits, + GoalCost +) +{GetSubQuery(selectQuery, isUsingAprdrg)}"; + return sql; + } + + private static string GetSubQueryForDistinctEncounter(string selectQuery, bool isUsingAprdrg) => $@"SELECT + ""dsspesEncounterId"", + ""dsspesDischargeDateTime"", + ""dsspesEntityID"", + ""dsspesPhysicianId"", + ""dsspesPrimaryServiceLineId"", + ""pblidDepartmentId"", + ""physPrimaryPhysicianSpecialtyId"", + ""ccChargeCodeID"", + ""ccCostDriver"", + {GetCaseTypeFamilyIdField(isUsingAprdrg)} + FROM ({selectQuery}) + "; + + private static string GetSubQuery(string selectQuery, bool isUsingAprdrg) + { + return $@"SELECT + :opportunityId AS ""OpportunityId"", + :isBaseline AS ""IsBaseline"", + ""DischargeFiscalYearId"", + ""DischargeFiscalMonthId"", + ""EntityID"", + ""PrimaryPhysicianId"", + ""PrimaryServiceLineId"", + ""DepartmentId"", + ""PhysicianSpecialtyId"", + ""ChargeCodeID"", + ""CostDriver"", + ""CaseTypeFamilyId"", + SUM(""Units"") AS ""Units"", + SUM(""Cost"") AS ""Cost"", + 0 AS ""GoalUnits"", + 0 AS ""GoalCost"" +FROM ( +SELECT + YEAR(TO_DATE(""dsspesDischargeDateTime"")) AS ""DischargeFiscalYearId"", + MONTH(TO_DATE(""dsspesDischargeDateTime"")) AS ""DischargeFiscalMonthId"", + ""dsspesEntityID"" AS ""EntityID"", + ""dsspesPhysicianId"" AS ""PrimaryPhysicianId"", + ""dsspesPrimaryServiceLineId"" AS ""PrimaryServiceLineId"", + ""pblidDepartmentId"" AS ""DepartmentId"", + ""physPrimaryPhysicianSpecialtyId"" AS ""PhysicianSpecialtyId"", + ""ccChargeCodeID"" AS ""ChargeCodeID"", + ""ccCostDriver"" AS ""CostDriver"", + {GetCaseTypeFamilyIdField(isUsingAprdrg)}, + ""Units"", + ""Cost"" +FROM ({selectQuery}) +) +GROUP BY + ""DischargeFiscalYearId"", + ""DischargeFiscalMonthId"", + ""EntityID"", + ""PrimaryPhysicianId"", + ""PrimaryServiceLineId"", + ""DepartmentId"", + ""PhysicianSpecialtyId"", + ""ChargeCodeID"", + ""CostDriver"", + ""CaseTypeFamilyId"""; + } + + private static string InjectChargeCodeJoin(long opportunityId, string selectQuery, string joinSql = null, string anchorSql = null, string chargeCodeAlias = "CC", string chargeCodeIdsTable = null) + { + if (string.IsNullOrEmpty(chargeCodeIdsTable)) chargeCodeIdsTable = $"cci.StrategicChargeCodeIds_{opportunityId}"; + + var anchor = anchorSql.IsNullOrEmpty() ? "INNER JOIN FW.DIMCHARGECODE AS CC ON CC.chargecodeid = PBLID.chargecodeid" : anchorSql; + var injectJoin = joinSql.IsNullOrEmpty() ? $"INNER JOIN {chargeCodeIdsTable} CCID ON {chargeCodeAlias}.chargecodeid = CCID.chargecodeid" : joinSql; + var insertPositions = new List(); + int searchStart = 0; + while (true) + { + int index = selectQuery.IndexOf(anchor, searchStart, StringComparison.OrdinalIgnoreCase); + if (index == -1) + { + break; + } + var endPosition = index + anchor.Length; + insertPositions.Add(endPosition); + searchStart = endPosition; + } + + if (insertPositions.Count == 0) return selectQuery; + var sb = new StringBuilder(selectQuery); + + for (int i = insertPositions.Count - 1; i >= 0; i--) + { + sb.Insert(insertPositions[i], Environment.NewLine + injectJoin); + } + return sb.ToString(); + } + + + private static string GetCaseTypeFamilyIdField(bool isUsingAprdrg) + { + var drgColumn = isUsingAprdrg ? "\"dsspesAPRDRGCaseTypeFamilyId\"" : "\"dsspesMSDRGCaseTypeFamilyId\""; + return $@" + (CASE WHEN ""ptRollupName"" = 'Outpatient' THEN ""dsspesCPTCodeCaseTypeFamilyId"" ELSE {drgColumn} + END) AS ""CaseTypeFamilyId"""; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityGLDetailQueryBuilder.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityGLDetailQueryBuilder.cs new file mode 100644 index 0000000..12d173f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityGLDetailQueryBuilder.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries +{ + internal class StrategicOpportunityGLDetailQueryBuilder + { + private const int InClauseThreshold = 5000; + private const string FwGLTable = "fw.factglsampled"; + + public string BuildDeleteQuery(long? opportunityId = null) + { + return $"DELETE FROM {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} WHERE OpportunityId = {(opportunityId.HasValue ? opportunityId.Value : ":opportunityId")}"; + } + + public string BuildInsertQuery(long opportunityId, string sourceTable, IEnumerable departmentIds, IEnumerable revenueAccountIds, IEnumerable expenseAccountIds, IEnumerable statisticAccountIds, DateOnly baselineStart, DateOnly baselineEnd) + { + var deptIds = departmentIds.ToList(); + var allAcctIds = revenueAccountIds.Concat(expenseAccountIds).Concat(statisticAccountIds).Distinct().ToList(); + var valueColumn = GetValueColumn(sourceTable); + var accountTypeMap = BuildAccountTypeMapSubquery(revenueAccountIds, expenseAccountIds, statisticAccountIds); + var sb = new StringBuilder(); + + BuildTempTables(sb, deptIds, allAcctIds); + + sb.AppendLine($@" +INSERT INTO {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} ( + OpportunityId, + IsBaseline, + FiscalYearId, + FiscalMonthId, + EntityId, + DepartmentId, + AccountId, + AccountType, + Amount, + GoalAmount, + DistributedAmount +) +SELECT + {opportunityId} AS OpportunityId, + TRUE AS IsBaseline, + gl.FiscalYearId, + gl.FiscalMonthId, + dd.EntityId, + gl.DepartmentId, + gl.AccountId, + at.AccountType, + SUM(gl.{valueColumn}) AS Amount, + 0 AS GoalAmount, + 0 AS DistributedAmount +FROM {sourceTable} gl +JOIN fw.DimDepartment dd ON dd.DepartmentId = gl.DepartmentId +JOIN {accountTypeMap} at ON at.AccountId = gl.AccountId"); + + AppendJoinsAndWhere(sb, deptIds, allAcctIds, baselineStart, baselineEnd); + + sb.AppendLine(@"GROUP BY + gl.FiscalYearId, + gl.FiscalMonthId, + dd.EntityId, + gl.DepartmentId, + gl.AccountId, + at.AccountType;"); + + return sb.ToString(); + } + + public string BuildMergeQuery(long opportunityId, string sourceTable, IEnumerable departmentIds, IEnumerable revenueAccountIds, IEnumerable expenseAccountIds, IEnumerable statisticAccountIds, DateOnly trackingStart, DateOnly trackingEnd) + { + var deptIds = departmentIds.ToList(); + var allAcctIds = revenueAccountIds.Concat(expenseAccountIds).Concat(statisticAccountIds).Distinct().ToList(); + var valueColumn = GetValueColumn(sourceTable); + var accountTypeMap = BuildAccountTypeMapSubquery(revenueAccountIds, expenseAccountIds, statisticAccountIds); + var sb = new StringBuilder(); + + BuildTempTables(sb, deptIds, allAcctIds); + + sb.AppendLine($@" +MERGE INTO {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} t +USING ( + SELECT + {opportunityId} AS OpportunityId, + FALSE AS IsBaseline, + gl.FiscalYearId, + gl.FiscalMonthId, + dd.EntityId, + gl.DepartmentId, + gl.AccountId, + at.AccountType, + SUM(gl.{valueColumn}) AS Amount + FROM {sourceTable} gl + JOIN fw.DimDepartment dd ON dd.DepartmentId = gl.DepartmentId + JOIN {accountTypeMap} at ON at.AccountId = gl.AccountId"); + + AppendJoinsAndWhere(sb, deptIds, allAcctIds, trackingStart, trackingEnd, " "); + + sb.AppendLine(@" GROUP BY + gl.FiscalYearId, + gl.FiscalMonthId, + dd.EntityId, + gl.DepartmentId, + gl.AccountId, + at.AccountType +) s +ON t.OpportunityId = s.OpportunityId + AND t.IsBaseline = s.IsBaseline + AND t.FiscalYearId = s.FiscalYearId + AND t.FiscalMonthId = s.FiscalMonthId + AND t.EntityId = s.EntityId + AND t.DepartmentId = s.DepartmentId + AND t.AccountId = s.AccountId + AND t.AccountType = s.AccountType +WHEN MATCHED THEN + UPDATE SET + t.Amount = s.Amount +WHEN NOT MATCHED THEN + INSERT ( + OpportunityId, IsBaseline, FiscalYearId, FiscalMonthId, + EntityId, DepartmentId, AccountId, AccountType, Amount, GoalAmount, DistributedAmount + ) + VALUES ( + s.OpportunityId, s.IsBaseline, s.FiscalYearId, s.FiscalMonthId, + s.EntityId, s.DepartmentId, s.AccountId, s.AccountType, s.Amount, 0, 0 + );"); + + return sb.ToString(); + } + + private static string BuildAccountTypeMapSubquery(IEnumerable revenueIds, IEnumerable expenseIds, IEnumerable statisticIds) + { + var rows = revenueIds.Select(id => $"({id}, 0)") + .Concat(expenseIds.Select(id => $"({id}, 1)")) + .Concat(statisticIds.Select(id => $"({id}, 2)")) + .ToList(); + + if (!rows.Any()) + return "(SELECT AccountId, AccountType FROM (VALUES (0, 0)) AS t(AccountId, AccountType) WHERE 1=0)"; + + return $"(SELECT AccountId, AccountType FROM (VALUES {string.Join(", ", rows)}) AS t(AccountId, AccountType))"; + } + + private static string GetValueColumn(string sourceTable) + { + return sourceTable.Equals(FwGLTable, StringComparison.OrdinalIgnoreCase) ? "Dollars" : "Value"; + } + + private static void BuildTempTables(StringBuilder sb, List deptIds, List acctIds) + { + if (deptIds.Count > InClauseThreshold) + { + sb.AppendLine("CREATE TEMPORARY TABLE tmp_gl_dept_ids (Id INT);"); + foreach (var batch in BatchIds(deptIds)) + { + sb.AppendLine($"INSERT INTO tmp_gl_dept_ids VALUES {string.Join(",", batch.Select(id => $"({id})"))};"); + } + } + + if (acctIds.Count > InClauseThreshold) + { + sb.AppendLine("CREATE TEMPORARY TABLE tmp_gl_acct_ids (Id INT);"); + foreach (var batch in BatchIds(acctIds)) + { + sb.AppendLine($"INSERT INTO tmp_gl_acct_ids VALUES {string.Join(",", batch.Select(id => $"({id})"))};"); + } + } + } + + private static void AppendJoinsAndWhere(StringBuilder sb, List deptIds, List acctIds, DateOnly periodStart, DateOnly periodEnd, string indent = "") + { + if (deptIds.Count > InClauseThreshold) + sb.AppendLine($"{indent}JOIN tmp_gl_dept_ids td ON gl.DepartmentId = td.Id"); + if (acctIds.Count > InClauseThreshold) + sb.AppendLine($"{indent}JOIN tmp_gl_acct_ids ta ON gl.AccountId = ta.Id"); + + var conditions = new List(); + + if (deptIds.Count <= InClauseThreshold) + conditions.Add($"gl.DepartmentId IN ({string.Join(",", deptIds)})"); + if (acctIds.Count <= InClauseThreshold) + conditions.Add($"gl.AccountId IN ({string.Join(",", acctIds)})"); + + conditions.Add("gl.IsDeleted = FALSE"); + //TimeClassId 1 = Actual + conditions.Add("gl.TimeClassId = 1"); + + if (periodStart.Year == periodEnd.Year) + { + conditions.Add($"gl.FiscalYearId = {periodStart.Year}"); + conditions.Add($"gl.FiscalMonthId BETWEEN {periodStart.Month} AND {periodEnd.Month}"); + } + else + { + conditions.Add($"gl.FiscalYearId BETWEEN {periodStart.Year} AND {periodEnd.Year}"); + conditions.Add($@"NOT ( +{indent} (gl.FiscalYearId = {periodStart.Year} AND gl.FiscalMonthId < {periodStart.Month}) +{indent} OR (gl.FiscalYearId = {periodEnd.Year} AND gl.FiscalMonthId > {periodEnd.Month}) +{indent} )"); + } + + sb.AppendLine($"{indent}WHERE {string.Join($"\n{indent} AND ", conditions)}"); + } + + private static IEnumerable> BatchIds(List ids, int batchSize = 1000) + { + for (var i = 0; i < ids.Count; i += batchSize) + { + yield return ids.GetRange(i, Math.Min(batchSize, ids.Count - i)); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityGLServiceQuery.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityGLServiceQuery.cs new file mode 100644 index 0000000..905b1c0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityGLServiceQuery.cs @@ -0,0 +1,37 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.Utilities; +using System.Collections.Generic; +using System.Reflection; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries +{ + internal class StrategicOpportunityGLServiceQuery + { + public static Dictionary GetDetailQuery(IEnumerable dimensions, bool onlyDimensions, string queryWhere = null) + { + var dicSql = new Dictionary(); + var assembly = Assembly.GetExecutingAssembly(); + var queryDetailList = ApplyQueryWhere(queryWhere, assembly.ReadResource("StrategicOpportunityGLDetailCTE.sql")); + var sql = string.Empty; + foreach (var dimension in dimensions) + { + if (StrategicOpportunityService.GLDimensionsConfig.TryGetValue(dimension, out var dimensionConfig)) + { + sql = ApplyQueryListOfEncounters(queryDetailList, assembly.ReadResource(dimensionConfig.SqlFile)); + dicSql.Add(dimension.ToString(), sql); + } + } + + if (!onlyDimensions) + { + sql = ApplyQueryListOfEncounters(queryDetailList, assembly.ReadResource("SelectStrategicOpportunityGLDetail.sql")); + dicSql.Add("detail", sql); + } + + return dicSql; + } + + private static string ApplyQueryListOfEncounters(string queryDetailList, string sql) => sql.Replace("-- {queryDetailList}", queryDetailList); + private static string ApplyQueryWhere(string queryWhere, string sql) => sql.Replace("-- {queryWhere}", string.IsNullOrEmpty(queryWhere) ? string.Empty : queryWhere); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityGLUpdateQueryBuilder.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityGLUpdateQueryBuilder.cs new file mode 100644 index 0000000..a990e99 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityGLUpdateQueryBuilder.cs @@ -0,0 +1,195 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries +{ + public class StrategicOpportunityGLUpdateQueryBuilder + { + private const string DETAIL_TABLE_NAME = "cci.FactStrategicGLDetail"; + private const string KEY_SEPARATOR = "|"; + + public (string Query, Dictionary Parameters) BuildUpdateQuery(IEnumerable adjustments, long opportunityId, IEnumerable rows) + { + var adjustmentList = adjustments.ToList(); + var parameters = new Dictionary + { + { "OpportunityId", opportunityId }, + { "IsBaseline", false } + }; + + var finalQuery = string.Empty; + for (var index = 0; index < adjustmentList.Count; index++) + { + var adjustment = adjustmentList[index]; + var (selectClause, selectParams) = GetSelectClause(opportunityId, adjustment, rows, index); + var joinClause = GetJoinClause(finalQuery); + var (whereClause, whereParams) = index == 0 ? + GetWhereClauseWithParameters(opportunityId, adjustment.NodeKey, rows, index) : + (string.Empty, new Dictionary()); + + finalQuery = $@" + {selectClause} + {joinClause} + {whereClause} + "; + + foreach (var param in selectParams.Concat(whereParams).Where(p => !parameters.ContainsKey(p.Key))) + { + parameters[param.Key] = param.Value; + } + } + + var updateQuery = $@" + UPDATE {DETAIL_TABLE_NAME} detail + SET + GoalAmount = COALESCE(tem.GoalAmount, detail.GoalAmount) + FROM ( + {finalQuery} + ) tem + where tem.RowId = detail.RowId + "; + + return (updateQuery, parameters); + } + + private (string SelectClause, Dictionary Parameters) GetSelectClause(long opportunityId, StrategicOpportunityGLAdjustment adjustment, IEnumerable rows, int index) + { + var parameters = new Dictionary(); + var isFirstAdj = index == 0; + var (goalAmountSelect, amountParams) = GetGoalSelect(adjustment.GoalAmountValue, index, isFirstAdj); + + foreach (var param in amountParams) + { + parameters[param.Key] = param.Value; + } + + var (whereClause, whereParams) = GetWhereClauseWithParameters(opportunityId, adjustment.NodeKey, rows, index, adjustment.AccountType); + foreach (var param in whereParams) + { + parameters[param.Key] = param.Value; + } + + var selectClause = $@" + SELECT + {(isFirstAdj ? "t1.RowId AS RowId" : "COALESCE(t1.RowId, t2.RowId) AS RowId")}, + {goalAmountSelect} + FROM {(isFirstAdj ? DETAIL_TABLE_NAME : $"( SELECT RowId, GoalAmount FROM {DETAIL_TABLE_NAME} \n{whereClause} )")} AS t1 + "; + + return (selectClause, parameters); + } + + private static (string GoalSelect, Dictionary Parameters) GetGoalSelect(decimal? value, int index, bool isFirstAdj) + { + var parameters = new Dictionary(); + string goalSelect; + + if (value != null) + { + var paramName = $"AmountValue{index}"; + parameters[paramName] = value.Value; + + goalSelect = isFirstAdj ? + $@" + SUM(t1.GoalAmount) OVER () AS ""SumAmount{index}"", + CASE WHEN ""SumAmount{index}"" != 0 THEN (t1.GoalAmount / ""SumAmount{index}"") * :{paramName} ELSE :{paramName} / COUNT(*) OVER() END AS GoalAmount + " : + $@" + (CASE + WHEN t1.GoalAmount IS NOT NULL AND t2.GoalAmount IS NOT NULL THEN t2.GoalAmount + WHEN t1.GoalAmount IS NOT NULL THEN t1.GoalAmount + ELSE NULL + END) AS _goalAmount{index}, + SUM(CASE WHEN _goalAmount{index} IS NOT NULL AND t1.RowId IS NOT NULL THEN _goalAmount{index} ELSE 0 END) OVER () AS ""SumAmount{index}"", + (CASE + WHEN ""SumAmount{index}"" = 0 AND t1.RowId IS NOT NULL THEN :{paramName} / COUNT(*) OVER() + WHEN _goalAmount{index} IS NOT NULL THEN (_goalAmount{index} / ""SumAmount{index}"") * :{paramName} + ELSE t2.GoalAmount + END) AS GoalAmount + "; + } + else + { + goalSelect = isFirstAdj ? "GoalAmount" : "t2.GoalAmount AS GoalAmount"; + } + + return (goalSelect, parameters); + } + + private string GetJoinClause(string joinCondition) + { + return !string.IsNullOrEmpty(joinCondition) ? $@"FULL OUTER JOIN ({joinCondition}) AS t2 ON t1.RowId = t2.RowId" : string.Empty; + } + + private (string WhereClause, Dictionary Parameters) GetWhereClauseWithParameters(long opportunityId, string nodeKey, IEnumerable rows, int adjustmentIndex, byte? accountType = null) + { + if (opportunityId <= 0) + throw new ArgumentException("Invalid opportunity ID", nameof(opportunityId)); + + if (string.IsNullOrWhiteSpace(nodeKey)) + throw new ArgumentException("Node key cannot be null or empty", nameof(nodeKey)); + + var parameters = new Dictionary + { + { "OpportunityId", opportunityId }, + { "IsBaseline", false } + }; + + if (nodeKey != "root") + { + var hierarchyIds = nodeKey.Split(KEY_SEPARATOR); + var filters = new List { "OpportunityId = :OpportunityId", "IsBaseline = :IsBaseline" }; + + for (var paramIndex = 0; paramIndex < hierarchyIds.Length; paramIndex++) + { + var id = hierarchyIds[paramIndex]; + + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException("Hierarchy ID cannot be null or empty"); + + var rowIndex = rows.Contains(StrategicOpportunityDetailDimension.System.Value) ? paramIndex + 1 : paramIndex; + var row = rows.ElementAt(rowIndex); + var dimension = StrategicOpportunityDetailDimension.GetFromString(row); + + if (dimension == null) + throw new ArgumentException($"Invalid dimension row: {row}"); + + if (!StrategicOpportunityService.GLDimensionsConfig.TryGetValue(dimension, out var dimensionConfig)) + throw new ArgumentException($"Dimension configuration not found for: {dimension}"); + + var columnName = dimensionConfig.ColumnName; + + if (string.IsNullOrWhiteSpace(columnName)) + throw new ArgumentException($"Invalid column name for dimension: {dimension}"); + + var paramName = $"HierarchyId_{adjustmentIndex}_{paramIndex}"; + if (dimension == StrategicOpportunityDetailDimension.GLRollup) + filters.Add($"AccountId IN (SELECT AccountId FROM fw.DimAccount WHERE GLRollup = :{paramName})"); + else + filters.Add($"{columnName} = :{paramName}"); + parameters[paramName] = id; + } + + if (accountType.HasValue) + { + var accountTypeParamName = $"AccountType_{adjustmentIndex}"; + filters.Add($"AccountType = :{accountTypeParamName}"); + parameters[accountTypeParamName] = accountType.Value; + } + + return ($"WHERE {string.Join(" AND ", filters)}", parameters); + } + + var rootFilters = new List { "OpportunityId = :OpportunityId", "IsBaseline = :IsBaseline" }; + if (accountType.HasValue) + { + var accountTypeParamName = $"AccountType_{adjustmentIndex}"; + rootFilters.Add($"AccountType = :{accountTypeParamName}"); + parameters[accountTypeParamName] = accountType.Value; + } + return ($"WHERE {string.Join(" AND ", rootFilters)}", parameters); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityUpdateQueryBuilder.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityUpdateQueryBuilder.cs new file mode 100644 index 0000000..a2a4d41 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/Queries/StrategicOpportunityUpdateQueryBuilder.cs @@ -0,0 +1,193 @@ +using MoreLinq; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries +{ + public class StrategicOpportunityUpdateQueryBuilder + { + private const string DETAIL_TABLE_NAME = "cci.FactStrategicChargeCodeDetail"; + private const string KEY_SEPARATOR = "|"; + + private int adjustmentIndex = 0; + + public (string Query, Dictionary Parameters) BuildUpdateQuery(List adjustments, long opportunityId, IEnumerable rows) + { + var parameters = new Dictionary + { + { "OpportunityId", opportunityId }, + { "IsBaseline", false } + }; + + var finalQuery = string.Empty; + for (var index = 0; index < adjustments.Count; index++) + { + adjustmentIndex = index; // Update the class-level index + var adjustment = adjustments[index]; + var (selectClause, selectParams) = GetSelectClause(opportunityId, adjustment, rows, index); + var joinClause = GetJoinClause(finalQuery); + var (whereClause, whereParams) = index == 0 ? + GetWhereClauseWithParameters(opportunityId, adjustment.NodeKey, rows) : + (string.Empty, new Dictionary()); + + finalQuery = $@" + {selectClause} + {joinClause} + {whereClause} + "; + + // Merge parameters from selectClause and whereClause + foreach (var param in selectParams.Concat(whereParams)) + { + if (!parameters.ContainsKey(param.Key)) + { + parameters[param.Key] = param.Value; + } + } + } + + var updateQuery = $@" + UPDATE {DETAIL_TABLE_NAME} detail + SET + GoalCost = COALESCE(tem.GoalCost, detail.GoalCost), + GoalUnits = COALESCE(tem.GoalUnits, detail.GoalUnits) + FROM ( + {finalQuery} + ) tem + where tem.RowId = detail.RowId + "; + + return (updateQuery, parameters); + } + + private (string SelectClause, Dictionary Parameters) GetSelectClause(long opportunityId, StrategicOpportunityChargeCodeAdjustment adjustment, IEnumerable rows, int index) + { + var parameters = new Dictionary(); + var isFirstAdj = index == 0; + var (goalCostSelect, costParams) = GetGoalSelect(adjustment.CostsValue, "Cost", index, isFirstAdj); + var (goalUnitsSelect, unitsParams) = GetGoalSelect(adjustment.UnitsValue, "Units", index, isFirstAdj); + + // Merge parameters + foreach (var param in costParams.Concat(unitsParams)) + { + parameters[param.Key] = param.Value; + } + + var (whereClause, whereParams) = GetWhereClauseWithParameters(opportunityId, adjustment.NodeKey, rows); + foreach (var param in whereParams) + { + parameters[param.Key] = param.Value; + } + + var selectClause = $@" + SELECT + {(isFirstAdj ? "t1.RowId AS RowId" : "COALESCE(t1.RowId, t2.RowId) AS RowId")}, + {goalCostSelect}, + {goalUnitsSelect} + FROM {(isFirstAdj ? DETAIL_TABLE_NAME : $"( SELECT RowId, GoalCost, GoalUnits FROM {DETAIL_TABLE_NAME} \n{whereClause} )")} AS t1 + "; + + return (selectClause, parameters); + } + + private static (string GoalSelect, Dictionary Parameters) GetGoalSelect(decimal? value, string columnName, int index, bool isFirstAdj) + { + var parameters = new Dictionary(); + string goalSelect; + + if (value != null) + { + var paramName = $"{columnName}Value{index}"; + parameters[paramName] = value.Value; + + goalSelect = isFirstAdj ? + $@" + SUM(t1.Goal{columnName}) OVER () AS ""Sum{columnName}{index}"", + CASE WHEN ""Sum{columnName}{index}"" != 0 THEN (t1.Goal{columnName} / ""Sum{columnName}{index}"") * :{paramName} ELSE :{paramName} / COUNT(*) OVER() END AS Goal{columnName} + " : + $@" + (CASE + WHEN t1.Goal{columnName} IS NOT NULL AND t2.Goal{columnName} IS NOT NULL THEN t2.Goal{columnName} + WHEN t1.Goal{columnName} IS NOT NULL THEN t1.Goal{columnName} + ELSE NULL + END) AS _goal{columnName}{index}, + SUM(CASE WHEN _goal{columnName}{index} IS NOT NULL AND t1.RowId IS NOT NULL THEN _goal{columnName}{index} ELSE 0 END) OVER () AS ""Sum{columnName}{index}"", + (CASE + WHEN ""Sum{columnName}{index}"" = 0 AND t1.RowId IS NOT NULL THEN :{paramName} / COUNT(t1.*) OVER() + WHEN _goal{columnName}{index} IS NOT NULL THEN (_goal{columnName}{index} / ""Sum{columnName}{index}"") * :{paramName} + ELSE t2.Goal{columnName} + END) AS Goal{columnName} + "; + } + else + { + goalSelect = isFirstAdj ? $"Goal{columnName}" : $"t2.Goal{columnName} AS Goal{columnName}"; + } + + return (goalSelect, parameters); + } + + private string GetJoinClause(string joinCondition) + { + return !string.IsNullOrEmpty(joinCondition) ? $@"FULL OUTER JOIN ({joinCondition}) AS t2 ON t1.RowId = t2.RowId" : string.Empty; + } + + private (string WhereClause, Dictionary Parameters) GetWhereClauseWithParameters(long opportunityId, string nodeKey, IEnumerable rows) + { + // Validate inputs + if (opportunityId <= 0) + throw new ArgumentException("Invalid opportunity ID", nameof(opportunityId)); + + if (string.IsNullOrWhiteSpace(nodeKey)) + throw new ArgumentException("Node key cannot be null or empty", nameof(nodeKey)); + + var parameters = new Dictionary + { + { "OpportunityId", opportunityId }, + { "IsBaseline", false } + }; + + if (nodeKey != "root") + { + var hierarchyIds = nodeKey.Split(KEY_SEPARATOR); + var filters = new List { "OpportunityId = :OpportunityId", "IsBaseline = :IsBaseline" }; + + for (var paramIndex = 0; paramIndex < hierarchyIds.Length; paramIndex++) + { + var id = hierarchyIds[paramIndex]; + + // Sanitize the ID value + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentException("Hierarchy ID cannot be null or empty"); + + var rowIndex = rows.Contains(StrategicOpportunityDetailDimension.System.Value) ? paramIndex + 1 : paramIndex; + var row = rows.ElementAt(rowIndex); + var dimension = StrategicOpportunityDetailDimension.GetFromString(row); + + if (dimension == null) + throw new ArgumentException($"Invalid dimension row: {row}"); + + if (!StrategicOpportunityService.DimensionsConfig.TryGetValue(dimension, out var dimensionConfig)) + throw new ArgumentException($"Dimension configuration not found for: {dimension}"); + + var columnName = dimensionConfig.ColumnName; + + if (string.IsNullOrWhiteSpace(columnName)) + throw new ArgumentException($"Invalid column name for dimension: {dimension}"); + + var paramName = $"HierarchyId_{adjustmentIndex}_{paramIndex}"; + filters.Add($"{columnName} = :{paramName}"); + parameters[paramName] = id; + } + + return ($"WHERE {string.Join(" AND ", filters)}", parameters); + } + + return ("WHERE OpportunityId = :OpportunityId AND IsBaseline = :IsBaseline", parameters); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/STRATEGIC_OPPORTUNITIES.md b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/STRATEGIC_OPPORTUNITIES.md new file mode 100644 index 0000000..9f96e03 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/STRATEGIC_OPPORTUNITIES.md @@ -0,0 +1,165 @@ +# Strategic Opportunities — Backend Overview + +## Domain Summary + +Strategic Opportunities are hospital cost/revenue improvement targets that progress through a lifecycle +from analysis (`Opportunity`) to active tracking (`Initiative`) to completion. Two opportunity types +exist with parallel implementations: **Charge Code** (encounter/charge-code-based) and **General Ledger** +(GL account-based). + +Data is persisted across three systems: +- **Central DB (PostgreSQL)** — entity metadata, measures, ramp-ups, roles, workbooks +- **Snowflake (cci schema)** — fact detail rows for grids and reporting +- **Snowflake DimStrategicItem** — warehouse dimension table for analytics/reporting + +--- + +## Status Lifecycle + +``` +NewOpportunity (0) + InValidation (1) + PendingApproval (2) + ↓ ConvertToInitiative +NewInitiative (3) + Tracking (4) + ↓ CompleteInitiative + Complete (5) + CompleteMonitoring (6) +``` + +**Key derived property:** `IsOpportunity = Status < NewInitiative (3)` + +This boolean gates which list endpoint returns the record (`GetStrategicOpportunitiesAsync` vs +`GetStrategicInitiativesAsync`) and which detail population method is called. + +**Status transitions trigger backend side-effects:** +- `NewOpportunity → NewInitiative`: calls `PopulateStrategicOpportunityCommittedDetails` with stage + `FromOpportunityToInitiative`, enqueues Hangfire tracking job, syncs DimStrategicItem +- `NewInitiative → Complete`: calls `CompleteInitiativeAsync`, sets `TrackingEndDate`, syncs DimStrategicItem + +--- + +## Two-Type Strategy Pattern + +### Inheritance + +``` +StrategicOpportunity (Table: strategic_opportunity) +├── StrategicOpportunityChargeCode (Table: strategic_opportunity_charge_code) +└── StrategicOpportunityGL (Table: strategic_opportunity_gl) +``` + +Both subtypes use **table-per-type** mapping. Queries always load through `_centralDbContext.StrategicOpportunityChargeCodes` +or `_centralDbContext.StrategicOpportunityGLs` — never through the base `StrategicOpportunities` DbSet +(which is used only for existence checks and deletes). + +### Parallel Service Implementations + +| Concern | Charge Code | GL | +|---|---|---| +| Detail service | `IStrategicChargeCodeOpportunityDetailService` | `IStrategicGLOpportunityDetailService` | +| Snowflake fact table | `cci.FactStrategicChargeCodeDetail` | `cci.FactStrategicGLDetail` | +| Dimension config | `DimensionsConfig` (8 dims) | `GLDimensionsConfig` (3 dims) | +| Distribution process | `DistributionProcessChargeCode` | `DistributionProcessGL` | + +Both are orchestrated by `StrategicOpportunityService`, which checks type and delegates accordingly. + +### Charge Code Specific Fields +- `PatientPopulationHPath` / `ExplorationPopulationId` — patient population source (HPath = hierarchy path; ExplorationPopulationId = exploration-based) +- `ChargeCodeHPath` — comma-separated hierarchy paths for selected charge codes +- Dimension set: Entity, PSL, CaseTypeFamily, Physician, PhysicianSpecialty, CostDriver, Department, ChargeCode + +### GL Specific Fields +- `AccountType` (Revenue/Expense/Statistic) — determines which account hierarchy columns apply +- `DepartmentHPath`, `RevenueAccountHPath`, `ExpenseAccountHPath`, `StatisticAccountHPath` +- `HasStatistic` — whether statistic accounts are included +- Dimension set: Entity, Department, GLAccount + +--- + +## Three-DB Write Pattern + +Every mutating operation must coordinate across three targets. The general flow: + +``` +1. Central DB transaction (EF Core) + └── Save entity + dependent records +2. Snowflake fact rows + └── DistributionProcess or DetailService populates cci.Fact* table +3. DimStrategicItem sync + └── strategicItemDimensionSyncService.UpsertAsync() / DeleteAsync() +``` + +**Important:** Snowflake writes happen outside the Central DB transaction. If Snowflake fails after +Central DB commits, the error is caught and logged but does NOT roll back Central DB. This is +intentional — Snowflake is considered a derived/reporting store. + +--- + +## Create Flow (Charge Code) + +``` +CreateOpportunityChargeCodeAsync() + 1. Map DTO → StrategicOpportunityChargeCode + 2. Resolve strataId via IIdServiceClient + 3. Save to Central DB + 4. opportunityRoleAssignmentService.AddPermissionsFromDefaultsAsync() ← adds default roles + 5. strategicChargeCodeOpportunityDetailService.PopulateStrategicOpportunityDetails() ← baseline data + 6. strategicItemDimensionSyncService.UpsertAsync() ← warehouse sync +``` + +GL creation follows the same pattern via `CreateOpportunityGeneralLedgerAsync`. + +--- + +## Delete Flow (Charge Code) + +Delete uses `CreateExecutionStrategy()` for retry-safe transactions: + +``` +DeleteChargeCodeOpportunityAsync() + Central DB transaction: + 1. ExecuteDeleteAsync: Measures, Workbooks, Comments, RampUps + 2. Remove ChargeCode specification entity + 3. Remove base StrategicOpportunity entity + 4. Commit + If committed successfully: + 5. StrategicOpportunityDetailQueryBuilder.BuildDeleteQuery() → Snowflake + 6. strategicItemDimensionSyncService.DeleteAsync() +``` + +Steps 5-6 failures are caught/logged and do not throw — Central DB state is authoritative. + +--- + +## Date Periods + +Three named periods are used throughout the system: + +| Period | Fields | Description | +|---|---|---| +| Baseline | `BaselineStartDate` / `BaselineEndDate` | Historical reference data period | +| Goal | `GoalStartDate` / `GoalEndDate` (computed) | `BaselineStart+1mo` to `BaselineStart+EstimatedDuration mo` | +| Tracking | `TrackingStartDate` / `TrackingEndDate` | Actual monitoring period, set when converting to initiative | + +`GoalStartDate` and `GoalEndDate` are `[NotMapped]` computed properties on `StrategicOpportunity` — they +are never stored in the DB. `GetTrackingEndDateForFilters()` returns the first day of the month _after_ +`TrackingEndDate` (exclusive upper bound for Snowflake range queries). + +--- + +## Key Files + +| File | Purpose | +|---|---| +| `StrategicOpportunity.cs` | Base entity, all shared fields and computed properties | +| `StrategicOpportunityChargeCode.cs` | Charge code subtype entity | +| `StrategicOpportunityGL.cs` | GL subtype entity | +| `StrategicOpportunityService.cs` | Main orchestrator — CRUD, status transitions, layout, copy | +| `StrategicChargeCodeOpportunityDetailService.cs` | Charge code detail population and retrieval | +| `StrategicGLOpportunityDetailService.cs` | GL detail population and retrieval | +| `StrategicItemDimensionSyncService.cs` | Warehouse dimension upsert/delete | +| `StrategicOpportunityJob.cs` | Hangfire background tracking runner | +| `Enums/StrategicOpportunityStatus.cs` | Lifecycle status enum (values 0–6) | +| `Enums/DistributionStage.cs` | Controls which distribution path executes | diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicChargeCodeExportHelper.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicChargeCodeExportHelper.cs new file mode 100644 index 0000000..dff212a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicChargeCodeExportHelper.cs @@ -0,0 +1,66 @@ +using ClosedXML.Excel; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Data; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public static class StrategicChargeCodeExportHelper + { + public static readonly int TableStartRowIndex = 2; + public static readonly ImmutableDictionary LayoutToRows = new Dictionary() + { + { "entity", "EntityName" }, + { "caseTypeFamily", "CaseTypeFamily" }, + { "primaryServiceLine", "PrimaryServiceLine" }, + { "physician", "PhysicianName" }, + { "physicianSpecialty", "PhysicianSpecialty" }, + { "costDriver", "CostDriver" }, + { "department", "DepartmentName" }, + { "chargeCode", "ChargeCode" }, + }.ToImmutableDictionary(); + + public static void SetRowData(DataRow row, string key, IEnumerable rows, Dictionary> dimensionsData) + { + var keys = key.Split('|'); + + rows.Where(r => r != StrategicOpportunityDetailDimension.System.Value) + .ForEach((r, i) => + { + var colName = LayoutToRows[r]; + var value = dimensionsData[r].FirstOrDefault(v => v.Key == keys[i]).Value; + row[colName] = value; + }); + } + + public static void StyleWorkSheet(IXLTable table, IXLRangeBase[] range) + { + range.ForEach(r => + { + r.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; + r.Style.Font.Bold = true; + }); + + table.Theme = XLTableTheme.None; + + var header = table.HeadersRow(); + header.Style.Font.Bold = true; + header.Style.Border.BottomBorder = XLBorderStyleValues.Thin; + } + } + + public class ExportHelper + { + public ExportHelper(string columnCaption, string columnFormat) + { + ColumnCaption = columnCaption; + ColumnFormat = columnFormat; + } + + public string ColumnCaption { get; set; } + public string ColumnFormat { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicChargeCodeOpportunityDetailService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicChargeCodeOpportunityDetailService.cs new file mode 100644 index 0000000..f4d2228 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicChargeCodeOpportunityDetailService.cs @@ -0,0 +1,1388 @@ +using ClosedXML.Excel; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Newtonsoft.Json; +using Strata.Analytics.Models.Workbooks.WorksheetContent.Fields; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Exploration; +using Strata.ContinuousImprovement.Biz.Exploration.Filters; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.Shared; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.DataSchema.Client; +using Strata.DataSchema.Models.Query; +using Strata.Schema.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using static Strata.ContinuousImprovement.Biz.Exploration.ExplorationService; +using static Strata.ContinuousImprovement.Biz.Export.ExportUtils; +using DataColumn = Strata.DataSchema.Models.Schema.DataColumn; +using DataTable = Strata.DataSchema.Models.Schema.DataTable; +using FilterGroup = Strata.DataSchema.Models.Query.FilterGroup; +using Row = Strata.DataSchema.Models.Query.Row; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicChargeCodeOpportunityDetailService( + IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IDataSchemaService dataSchemaService, + ISchemaServiceClient schemaServiceClient, + ISnowflakeDatabaseContext databaseContext, + IExplorationFilterService explorationFilterService, + IDistributionProcessFactory distributionProcessFactory, + IStrategicOpportunityDetailQueryBuilder strategicOpportunityDetailQueryBuilder, + IHubContext notificationHubContext, + IStrategicItemDimensionSyncService strategicItemDimensionSyncService, + ILogger logger = default) : JazzDbService(dbContextFactory, centralDbContext, claimsPrincipalAccessor), IStrategicChargeCodeOpportunityDetailService + { + + public async Task GetStrategicOpportunityDetailData(long opportunityId, bool isTracking, StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken) + { + var dimensions = getData.Dimensions.Select(x => StrategicOpportunityDetailDimension.GetFromString(x)).ToList(); + if (dimensions.Any(x => x == null)) return null; + + var strategicOpportunity = await _centralDbContext.StrategicOpportunityChargeCodes.FirstOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + if (strategicOpportunity == null) return null; + + var sqlWhere = string.Empty; + if (getData.DateStart.HasValue || getData.DateEnd.HasValue) + { + if (!getData.DateStart.HasValue) getData.DateStart = isTracking ? strategicOpportunity.TrackingStartDate : strategicOpportunity.GoalStartDate; + if (!getData.DateEnd.HasValue) getData.DateEnd = isTracking ? strategicOpportunity.TrackingEndDate : strategicOpportunity.GoalEndDate; + getData.SetFirstAndLastDayOfMonth(); + + var startYear = getData.DateStart.Value.Year; + var startMonth = getData.DateStart.Value.Month; + var endYear = getData.DateEnd.Value.Year; + var endMonth = getData.DateEnd.Value.Month; + sqlWhere = @$" + AND ( + (DischargeFiscalYearId > {startYear}) + OR (DischargeFiscalYearId = {startYear} AND DischargeFiscalMonthId >= {startMonth}) + ) + AND ( + (DischargeFiscalYearId < {endYear}) + OR (DischargeFiscalYearId = {endYear} AND DischargeFiscalMonthId <= {endMonth}) + ) + "; + } + + var dicDimensionSql = StrategicOpportunityServiceQuery.GetDetailQuery(dimensions, false, sqlWhere); + var queryCount = dicDimensionSql.Count; + if (queryCount == 0) return null; + var querySelect = String.Join(Environment.NewLine, dicDimensionSql.Values); + + var parametersReplace = new Dictionary + { + { "@OpportunityId", opportunityId.ToString() }, + { "@IsBaseline", isTracking ? "False" : "NULL" } + }; + foreach (var x in parametersReplace) + { + querySelect = querySelect.Replace(x.Key, x.Value); + } + + var queryOneParams = new Dictionary + { + { "MULTI_STATEMENT_COUNT", queryCount } + }; + + var data = new StrategicOpportunityDetailData() + { + Data = [], + DimensionsData = dimensions.ToDictionary(x => x.ToString(), x => new Dictionary()), + EncounterData = new Dictionary() + }; + + try + { + var results = await databaseContext.QueryMultipleAsync(querySelect, "Entity Detail", queryOneParams); + foreach (var strDimension in dicDimensionSql.Keys) + { + if (data.DimensionsData.ContainsKey(strDimension)) + { + data.DimensionsData[strDimension] = results.Read>().ToDictionary(x => x.Key, x => x.Value); + } + } + + data.Data = results.Read(); + + var encounterData = await GetDistinctEncounterCount(strategicOpportunity, getData, cancellationToken); + data.EncounterData = encounterData ?? data.EncounterData; + } + catch (Exception ex) + { + logger.LogServiceError(ex, info: new Dictionary { { "query", querySelect }, { "queryParams", queryOneParams } }); + throw; + } + + return data; + } + + public async Task ExportInitiativeAsync(long opportunityId, bool isTracking, StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken) + { + CultureInfo.CurrentCulture = new CultureInfo(_user.GetCulture()); + + if (!getData.DateEnd.HasValue) + { + throw new ArgumentException("DateEnd must be provided when exporting a strategic initiative.", nameof(getData)); + } + var endDate = getData.DateEnd.Value; + + var strategicOpportunity = await _centralDbContext.StrategicOpportunities.FirstOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (strategicOpportunity == null || string.IsNullOrEmpty(strategicOpportunity.LayoutJSON)) + { + return null; + } + + var layout = JsonConvert.DeserializeObject(strategicOpportunity.LayoutJSON); + var periodList = await GetStrategicOpportunityDetailData(opportunityId, isTracking, getData, cancellationToken); + + getData.DateStart = endDate; + var monthList = await GetStrategicOpportunityDetailData(opportunityId, isTracking, getData, cancellationToken); + + var strategicInitiativeExport = new StrategicInitiativeChargeCodeExportService(layout, strategicOpportunity); + var exportOptions = strategicInitiativeExport.GetExportOptions(); + var dataList = strategicInitiativeExport.MapStrategicInitiativeToDataTable(periodList, monthList); + + var wb = CreateExcelWorkbook(dataList, exportOptions, StrategicChargeCodeExportHelper.TableStartRowIndex); + strategicInitiativeExport.StyleWorkSheet(wb, strategicOpportunity.TrackingStartDate, endDate); + return wb; + } + + public async Task ExportOpportunityAsync(long opportunityId, StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken) + { + CultureInfo.CurrentCulture = new CultureInfo(_user.GetCulture()); + var strategicOpportunity = await _centralDbContext.StrategicOpportunityChargeCodes.FirstOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (strategicOpportunity == null || string.IsNullOrEmpty(strategicOpportunity.LayoutJSON)) + { + return null; + } + + var layout = JsonConvert.DeserializeObject(strategicOpportunity.LayoutJSON); + var opportunitiesData = await GetStrategicOpportunityDetailData(opportunityId, false, getData, cancellationToken); + var strategicOpportunityExport = new StrategicOpportunityChargeCodeExportService(layout, strategicOpportunity); + var exportOptions = strategicOpportunityExport.GetExportOptions(); + var dataList = strategicOpportunityExport.MapStrategicOpportunityToDataTable(opportunitiesData); + var wb = CreateExcelWorkbook(dataList, exportOptions, StrategicChargeCodeExportHelper.TableStartRowIndex); + + strategicOpportunityExport.StyleWorkSheet(wb); + return wb; + } + + public async Task PopulateStrategicOpportunityDetails(StrategicOpportunityChargeCode opportunity, CancellationToken cancellationToken) + { + var queryConfig = await GetQueryConfig(opportunity, null, true, cancellationToken); + var selectQuery = await dataSchemaService.BuildSqlQuery(queryConfig, cancellationToken); + var parameters = selectQuery.Parameters + .Append(new KeyValuePair(":opportunityId", opportunity.OpportunityId)) + .Append(new KeyValuePair(":isBaseline", true)) + .ToList(); + + var idsList = await GetChargeCodeIds(opportunity.ChargeCodeHPath, cancellationToken); + await PopulateTransientChargeCodeIds(idsList, opportunity.OpportunityId, cancellationToken); + var deleteSql = strategicOpportunityDetailQueryBuilder.BuildDeleteQuery(); + var insertSql = strategicOpportunityDetailQueryBuilder.BuildInsertQuery(opportunity, selectQuery.SqlQuery, await IsUsingAprdrgAsync(cancellationToken)); + + await databaseContext.QueryAsync(deleteSql, $"{nameof(PopulateStrategicOpportunityDetails)}.Delete", parameters); + await databaseContext.QueryAsync(insertSql, $"{nameof(PopulateStrategicOpportunityDetails)}.Insert", parameters); + await CleanupTransientChargeCodeIds(opportunity); + + await PopulateStrategicOpportunityGoalDetails(opportunity, DistributionStage.FromSourceToOpportunity, true, cancellationToken); + } + + public async Task PopulateDetailGoalSummary(StrategicOpportunityChargeCode opportunity, double[] rampUps, bool isSaveChanges, CancellationToken cancellationToken) + { + if (rampUps == null) + { + rampUps = await _centralDbContext.StrategicOpportunityRampUps + .Where(x => x.OpportunityId == opportunity.OpportunityId) + .OrderBy(x => x.MonthNumber) + .Select(x => x.Percentage) + .ToArrayAsync(cancellationToken); + } + + var distributionProcessChargeCode = distributionProcessFactory.CreateDistributionProcessChargeCode( + opportunity.OpportunityId, + new Shared.Period(opportunity.BaselineStartDate, opportunity.BaselineEndDate), + new Shared.Period(opportunity.GoalStartDate, opportunity.GoalEndDate), + new Shared.Period(opportunity.TrackingStartDate, opportunity.TrackingEndDate), + rampUps + ); + + var distributionSummary = await distributionProcessChargeCode.GetDistributionSummaryAsync(false); + opportunity.TotalImprovement = distributionSummary.ImprovementSum; + opportunity.EstimatedGoal = distributionSummary.EstimatedGoalSum; + + if (isSaveChanges) await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateMeasuresDetails(StrategicOpportunityChargeCode opportunity, CancellationToken cancellationToken) + { + var collectionMeasures = _centralDbContext.Entry(opportunity).Collection(o => o.Measures); + if (!collectionMeasures.IsLoaded) + { + await collectionMeasures.LoadAsync(cancellationToken); + } + + var measures = new List(); + if (opportunity.Status == (byte)StrategicOpportunityStatus.CompleteMonitoring) + { + var primaryMeasure = opportunity.Measures.FirstOrDefault(m => m.MeasureGroup == Enums.MeasureGroup.Primary); + if (primaryMeasure != null) + measures.Add(primaryMeasure); + } + else + { + measures.AddRange(opportunity.Measures.Where(m => m.MeasureGroup != Enums.MeasureGroup.System)); + } + + foreach (var measure in measures) + { + await UpdateOneMeasureDetails(opportunity, measure, cancellationToken); + } + + if (opportunity.Status != (byte)StrategicOpportunityStatus.NewInitiative && opportunity.LastRunTrackingDate != null) + { + await UpdateSystemMeasures(opportunity, MeasureType.ActualCost, cancellationToken); + } + } + + public async Task UpdateMeasuresAverage(StrategicOpportunityChargeCode opportunity, CancellationToken cancellationToken) + { + var collectionMeasures = _centralDbContext.Entry(opportunity).Collection(o => o.Measures); + if (!collectionMeasures.IsLoaded) + { + await collectionMeasures.LoadAsync(cancellationToken); + } + + if (opportunity.Status == (byte)StrategicOpportunityStatus.CompleteMonitoring) + { + var primaryMeasure = opportunity.Measures.FirstOrDefault(m => m.MeasureGroup == Enums.MeasureGroup.Primary); + if (primaryMeasure != null) + { + var dateRange = new Period(opportunity.TrackingStartDate, opportunity.TrackingEndDate); + primaryMeasure.AverageValue = await GetOneMeasureAverage(opportunity, primaryMeasure, dateRange, cancellationToken); + } + + } + else + { + foreach (var measure in opportunity.Measures.Where(m => m.MeasureGroup != Enums.MeasureGroup.System)) + { + measure.AverageValue = await GetOneMeasureAverage(opportunity, measure, null, cancellationToken); + } + } + + await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + public async Task DeleteMeasuresDetails(long opportunityId, CancellationToken cancellationToken) + { + var measureIds = await _centralDbContext.StrategicOpportunityMeasures + .Where(x => x.OpportunityId == opportunityId) + .Select(x => x.MeasureId) + .ToListAsync(cancellationToken); + + if (measureIds.Count == 0) return; + + var query = strategicOpportunityDetailQueryBuilder.GetDeleteAllMeasuresSql(measureIds); + if (!string.IsNullOrEmpty(query)) + await databaseContext.ExecuteCommandAsync(query); + } + + public async Task PopulateTrackingDetails(StrategicOpportunityChargeCode opportunity, CancellationToken cancellationToken) + { + var hub = notificationHubContext.Clients.Group(opportunity.OpportunityId.ToString()); + + try + { + if (opportunity.Status == (byte)StrategicOpportunityStatus.Tracking) + { + var queryConfig = await GetQueryConfig(opportunity, null, true, cancellationToken); + var selectQuery = await dataSchemaService.BuildSqlQuery(queryConfig, cancellationToken); + var parameters = selectQuery.Parameters + .Append(new KeyValuePair(":opportunityId", opportunity.OpportunityId)) + .Append(new KeyValuePair(":isBaseline", false)) + .ToList(); + + if (string.IsNullOrEmpty(opportunity.PatientPopulationHPath)) + { + (QueryParams queryParams, string ctfSummaryQuery) = await GetExplorationQuery(opportunity.ExplorationPopulationId, opportunity.TrackingStartDate, opportunity.TrackingEndDate, cancellationToken); + selectQuery.SqlQuery = ApplyExplorationFilters(opportunity, selectQuery.SqlQuery, parameters, ctfSummaryQuery); + parameters.AddRange(queryParams.AsDictionary); + parameters = parameters.Append(new KeyValuePair(":CaseTypeFamilyId", queryParams.CaseTypeFamilyId)).ToList(); + } + + var idsList = await GetChargeCodeIds(opportunity.ChargeCodeHPath, cancellationToken); + await PopulateTransientChargeCodeIds(idsList, opportunity.OpportunityId, cancellationToken); + var mergeSql = strategicOpportunityDetailQueryBuilder.BuildMergeQuery(opportunity.OpportunityId, selectQuery.SqlQuery, await IsUsingAprdrgAsync(cancellationToken)); + await databaseContext.QueryAsync(mergeSql, $"{nameof(PopulateTrackingDetails)}.Merge", parameters); + await CleanupTransientChargeCodeIds(opportunity); + + var actualCost = await databaseContext.ExecuteScalarAsync($"SELECT SUM(cost) FROM cci.FactStrategicChargeCodeDetail WHERE isbaseline = FALSE AND opportunityid = {opportunity.OpportunityId}"); + var committedCost = await databaseContext.ExecuteScalarAsync(@$"SELECT SUM(goalcost) FROM cci.FactStrategicChargeCodeDetail WHERE isbaseline = FALSE + AND (dischargefiscalyearid < {DateTime.UtcNow.Year} OR (dischargefiscalyearid = {DateTime.UtcNow.Year} AND dischargefiscalmonthid <= {DateTime.UtcNow.Month})) AND opportunityid = {opportunity.OpportunityId}"); + + opportunity.ProgressToDateActual = actualCost; + opportunity.ProgressToDateCommitted = committedCost; + } + + if (!opportunity.IsOpportunity) + { + await UpdateMeasuresDetails(opportunity, cancellationToken); + } + + await UpdateLastRunTracking(opportunity, RunTrackingStatus.Success, cancellationToken); + await hub.SendStrategicInitiativeTrackerSuccess(opportunity.OpportunityId.ToString(), (byte)opportunity.LastRunTrackingStatus, opportunity.LastRunTrackingDate); + } + catch (Exception ex) + + { + await UpdateLastRunTracking(opportunity, RunTrackingStatus.Failed, cancellationToken); + await hub.SendStrategicInitiativeTrackerError(opportunity.OpportunityId.ToString(), (byte)opportunity.LastRunTrackingStatus, opportunity.LastRunTrackingDate); + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + public async Task UpdateStrategicOpportunityDetailsData + ( + long opportunityId, + IEnumerable adjustments, + IEnumerable rows, CancellationToken cancellationToken + ) + { + try + { + var opportunity = await _centralDbContext.StrategicOpportunityChargeCodes.SingleOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (opportunity == null) return false; + + var updateBuilder = new StrategicOpportunityUpdateQueryBuilder(); + var (updateQuery, parameters) = updateBuilder.BuildUpdateQuery(adjustments.ToList(), opportunityId, rows); + await databaseContext.QueryAsync(updateQuery, $"{nameof(UpdateStrategicOpportunityDetailsData)}.Update", parameters); + opportunity.Status = (byte)StrategicOpportunityStatus.InValidation; + + await PopulateDetailGoalSummary(opportunity, null, false, cancellationToken); + + await _centralDbContext.SaveChangesAsync(cancellationToken); + await strategicItemDimensionSyncService.UpsertAsync(opportunity, cancellationToken); + + return true; + } + catch (Exception ex) + { + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + public async Task PopulateStrategicOpportunityGoalDetails(StrategicOpportunityChargeCode opportunity, DistributionStage stage, bool isSaveChanges, CancellationToken cancellationToken) + { + var rampUps = await _centralDbContext.StrategicOpportunityRampUps + .Where(x => x.OpportunityId == opportunity.OpportunityId) + .OrderBy(x => x.MonthNumber) + .Select(x => x.Percentage) + .ToArrayAsync(cancellationToken); + + var distributionProcessChargeCode = distributionProcessFactory.CreateDistributionProcessChargeCode( + opportunity.OpportunityId, + new Shared.Period(opportunity.BaselineStartDate, opportunity.BaselineEndDate), + new Shared.Period(opportunity.GoalStartDate, opportunity.GoalEndDate), + new Shared.Period(opportunity.TrackingStartDate, opportunity.TrackingEndDate), + rampUps + ); + + var distributionSummary = await distributionProcessChargeCode.DistributeAsync(opportunity.BaselineDistribution, stage); + opportunity.TotalImprovement = distributionSummary.ImprovementSum; + opportunity.EstimatedGoal = distributionSummary.EstimatedGoalSum; + + if (isSaveChanges) await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + public async Task PopulateStrategicOpportunityCommittedDetails(StrategicOpportunityChargeCode opportunity, DistributionStage stage, bool isSaveChanges, CancellationToken cancellationToken) + { + var rampUps = await _centralDbContext.StrategicOpportunityRampUps + .Where(x => x.OpportunityId == opportunity.OpportunityId) + .OrderBy(x => x.MonthNumber) + .Select(x => x.Percentage) + .ToArrayAsync(cancellationToken); + + var distributionProcessChargeCode = distributionProcessFactory.CreateDistributionProcessChargeCode( + opportunity.OpportunityId, + new Shared.Period(opportunity.BaselineStartDate, opportunity.BaselineEndDate), + new Shared.Period(opportunity.GoalStartDate, opportunity.GoalEndDate), + new Shared.Period(opportunity.TrackingStartDate, opportunity.TrackingEndDate), + rampUps + ); + + var distributionSummary = await distributionProcessChargeCode.DistributeAsync(opportunity.BaselineDistribution, stage); + opportunity.TotalCommittedCost = distributionSummary.CommittedSum; + + if (!opportunity.IsOpportunity) + await UpdateSystemMeasures(opportunity, MeasureType.CommittedCost, cancellationToken); + + if (isSaveChanges) await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateDetailCommittedByRampUp(StrategicOpportunityChargeCode opportunity, double[] oldRampUps, double[] newRampUps, bool isSaveChanges, CancellationToken cancellationToken) + { + var distributionProcessChargeCode = distributionProcessFactory.CreateDistributionProcessChargeCode( + opportunity.OpportunityId, + new Shared.Period(opportunity.BaselineStartDate, opportunity.BaselineEndDate), + new Shared.Period(opportunity.GoalStartDate, opportunity.GoalEndDate), + new Shared.Period(opportunity.TrackingStartDate, opportunity.TrackingEndDate), + newRampUps + ); + + var distributionSummary = await distributionProcessChargeCode.UpdateDetailCommittedByRampUp(oldRampUps); + + await UpdateSystemMeasures(opportunity, MeasureType.CommittedCost, cancellationToken); + + opportunity.TotalCommittedCost = distributionSummary.CommittedSum; + + if (isSaveChanges) await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + private async Task UpdateOneMeasureDetails(StrategicOpportunityChargeCode opportunity, + StrategicOpportunityMeasure measure, CancellationToken cancellationToken) + { + var dateRange = new Period(opportunity.TrackingStartDate, opportunity.Status == (byte)StrategicOpportunityStatus.CompleteMonitoring && measure.MeasureGroup == MeasureGroup.Primary ? DateOnly.FromDateTime(DateTime.Now) : opportunity.TrackingEndDate); + var result = await GetSelectMeasureDetailsSql(opportunity, measure, dateRange, cancellationToken); + var sqlMerge = strategicOpportunityDetailQueryBuilder.WrapIntoMeasureMergeQuery(result.Query); + await databaseContext.ExecuteCommandAsync(sqlMerge, result.Parameters); + } + + private async Task GetOneMeasureAverage(StrategicOpportunityChargeCode opportunity, + StrategicOpportunityMeasure measure, Period dateRange, CancellationToken cancellationToken) + { + if (dateRange == null) + dateRange = new Period(opportunity.BaselineStartDate, opportunity.BaselineEndDate); + var result = await GetSelectMeasureDetailsSql(opportunity, measure, dateRange, cancellationToken); + var query = strategicOpportunityDetailQueryBuilder.WrapIntoMeasureAverageQuery(result.Query); + var average = await databaseContext.ExecuteScalarAsync(query, result.Parameters); + return average; + } + + private async Task<(string Query, List> Parameters)> GetSelectMeasureDetailsSql + ( + StrategicOpportunityChargeCode opportunity, + StrategicOpportunityMeasure measure, + Period dateRange, + CancellationToken cancellationToken + ) + { + var columns = new List + { + new("DischargeDateTime", "dsspes", "DischargeDateTime") + }; + if (MeasureTypeConstants.PER_CASE_MEASURE_TYPES.Contains(measure.MeasureType)) + { + columns.Add(new StrategicColumn("EncounterId", "dsspes", "dsspesEncounterId")); + } + if (MeasureTypeConstants.MARGIN_MEASURE_TYPES.Contains(measure.MeasureType)) + { + columns.Add(new StrategicColumn("NetRevenue", "dsspes", "dsspesNetRevenue")); + } + var chargeCodeIds = await GetChargeCodeIds(opportunity.ChargeCodeHPath, cancellationToken); + var measureFilters = measure.MeasureGroup == Enums.MeasureGroup.Primary ? + new List() : + JsonConvert.DeserializeObject>(measure.FiltersJSON); + + if (measure.MeasureGroup == Enums.MeasureGroup.Primary) + { + chargeCodeIds = chargeCodeIds.Intersect(JsonConvert.DeserializeObject>(measure.FiltersJSON)); + } + else + { + var chargeCodeFilter = measureFilters.FirstOrDefault(f => f.Key == ChipKey.ChargeCode); + if (chargeCodeFilter != null) + { + var filteredChargeCodeIds = await GetChargeCodeIds(string.Join(",", chargeCodeFilter.Filters.Select(f => f.Id)), cancellationToken); + chargeCodeIds = chargeCodeFilter.Operator == OperatorType.In ? chargeCodeIds.Intersect(filteredChargeCodeIds) : chargeCodeIds.Except(filteredChargeCodeIds); + } + } + var dataSourceId = await GetDataSourceId(cancellationToken); + var dataTables = await GetDataTablesForMeasure(opportunity, measure.MeasureType, dataSourceId, cancellationToken); + var rows = await GetRows(dataTables, columns, cancellationToken); + var values = await GetMeasureValues(measure.MeasureType, dataTables, cancellationToken); + var measureFilterDto = new MeasureFilterDto() { MeasureFilters = measureFilters, FiltersForMeasure = true, MeasureType = measure.MeasureType }; + var filtersResult = await GetBaseFiltersAsync(opportunity, dateRange, dataTables, measureFilterDto, cancellationToken); + + var queryConfig = new QueryConfig + { + Rows = rows, + Values = values.Select(v => v.ToSchemaValue()).ToList(), + FilterGroup = new FilterGroup() { Filters = filtersResult.FiltersList }, + WithTotals = false, + SkipPhiCheck = false, + DisplayZeroIfNull = true, + SkipResultAggregation = false, + PrependExtraMetadataRow = false, + UseSqlModelv2 = false, + DataSourceId = dataSourceId + }; + + var selectQuery = await dataSchemaService.BuildSqlQuery(queryConfig, cancellationToken); + if (filtersResult.HaveNoIntersectedIds) + { + selectQuery.SqlQuery = selectQuery.SqlQuery.Replace("WHERE", "WHERE 1 != 1 AND"); + } + + var sbQuery = new StringBuilder(); + sbQuery.AppendLine(GetSqlSelectChargeCodeIds(chargeCodeIds, true)); + + var selectSql = strategicOpportunityDetailQueryBuilder.GetSelectMeasureSql(opportunity.OpportunityId, measure.MeasureType, selectQuery.SqlQuery, measureFilterDto.MarginMeasureFilters); + var trackingMonths = DateExtensions.GetMonthDifference(dateRange.Start, dateRange.End, true); + var parameters = selectQuery.Parameters + .Append(new KeyValuePair(":measureId", measure.MeasureId)) + .Append(new KeyValuePair(":startDate", dateRange.Start.ToString(DateExtensions.MEASURE_FORMAT_YYYY_MM_DD))) + .Append(new KeyValuePair(":monthsNumber", trackingMonths)) + .ToList(); + + if (string.IsNullOrEmpty(opportunity.PatientPopulationHPath)) + { + (QueryParams queryParams, string ctfSummaryQuery) = await GetExplorationQuery(opportunity.ExplorationPopulationId, dateRange.Start, dateRange.End, cancellationToken); + selectSql = ApplyExplorationFilters(opportunity, selectSql, parameters, ctfSummaryQuery); + parameters.AddRange(queryParams.AsDictionary); + parameters = parameters.Append(new KeyValuePair(":CaseTypeFamilyId", queryParams.CaseTypeFamilyId)).ToList(); + } + + sbQuery.AppendLine(selectSql); + + return (sbQuery.ToString(), parameters); + } + + private async Task> GetDistinctEncounterCount(StrategicOpportunityChargeCode opportunity, StrategicOpportunityGetDetailData getData, CancellationToken cancellationToken) + { + var queryConfig = await GetQueryConfig(opportunity, new Period(getData.DateStart, getData.DateEnd), false, cancellationToken); + var selectQuery = await dataSchemaService.BuildSqlQuery(queryConfig, cancellationToken); + + var parameters = selectQuery.Parameters + .Append(new KeyValuePair(":opportunityId", opportunity.OpportunityId)) + .Append(new KeyValuePair(":isBaseline", false)) + .ToList(); + + if (!opportunity.IsOpportunity && string.IsNullOrEmpty(opportunity.PatientPopulationHPath)) + { + (QueryParams queryParams, string ctfSummaryQuery) = await GetExplorationQuery(opportunity.ExplorationPopulationId, opportunity.TrackingStartDate, opportunity.TrackingEndDate, cancellationToken); + selectQuery.SqlQuery = ApplyExplorationFilters(opportunity, selectQuery.SqlQuery, parameters, ctfSummaryQuery); + parameters.AddRange(queryParams.AsDictionary); + parameters = parameters.Append(new KeyValuePair(":CaseTypeFamilyId", queryParams.CaseTypeFamilyId)).ToList(); + } + + var sbQuery = new StringBuilder(); + + var idsList = await GetChargeCodeIds(opportunity.ChargeCodeHPath, cancellationToken); + sbQuery.AppendLine(GetSqlSelectChargeCodeIds(idsList, true)); + + sbQuery.AppendLine(strategicOpportunityDetailQueryBuilder.BuildGetDistinctEncounterCountQuery(opportunity.OpportunityId, selectQuery.SqlQuery, await IsUsingAprdrgAsync(cancellationToken), getData)); + + var result = (await databaseContext.QueryAsync>(sbQuery.ToString(), "Strategic Opportunity", parameters)) + .ToDictionary(x => x.Key, x => x.Value); + + return result; + } + + private async Task GetQueryConfig(StrategicOpportunityChargeCode strategicOpportunity, Period dateRange, bool isGetValues, CancellationToken cancellationToken) + { + var dataSourceId = await GetDataSourceId(cancellationToken); + var dataTables = await GetDataTablesForOpportunity(strategicOpportunity, isGetValues, dataSourceId, cancellationToken); + var rows = await GetRows(dataTables, GetColumns(), cancellationToken); + var values = isGetValues ? await GetValues(dataTables, cancellationToken) : new List(); + var filterGroup = await GetBaseFilterGroup(strategicOpportunity, dateRange, dataTables, new List(), cancellationToken); + + var queryConfig = new QueryConfig + { + Rows = rows, + Values = values.Select(v => v.ToSchemaValue()).ToList(), + FilterGroup = filterGroup, + WithTotals = false, + SkipPhiCheck = false, + DisplayZeroIfNull = true, + SkipResultAggregation = false, + PrependExtraMetadataRow = false, + UseSqlModelv2 = false, + DataSourceId = dataSourceId + }; + return queryConfig; + } + + private async Task GetBaseFilterGroup(StrategicOpportunityChargeCode strategicOpportunity, Period dateRange, List dataTables, + List measureFilters, CancellationToken cancellationToken) + { + var filterGroup = new FilterGroup(); + var measureFilterDto = new MeasureFilterDto() { MeasureFilters = measureFilters }; + var processedDateRange = GetProcessedDateRange(dateRange, strategicOpportunity, measureFilterDto); + filterGroup.Filters = (await GetBaseFiltersAsync(strategicOpportunity, processedDateRange, dataTables, measureFilterDto, cancellationToken)).FiltersList; + return filterGroup; + } + + private async Task> GetValues(List dataTables, CancellationToken cancellationToken) + { + var values = new List(); + var unitsOfServiceColumn = await GetDataColumn(new StrategicColumn("UnitsOfService", "pblid", "UnitsOfService"), dataTables, cancellationToken); + if (unitsOfServiceColumn != null) + { + values.Add(new QueryValue() + { + Calculation = $"SUM([{unitsOfServiceColumn.DataColumnId}])", + ColumnAlias = "Units" + }); + } + + var variableDirectCostColumn = await GetDataColumn(new StrategicColumn("VariableDirectCost", "costdetail", "VariableDirectCost"), dataTables, cancellationToken); + if (variableDirectCostColumn != null) + { + values.Add(new QueryValue() + { + Calculation = $"SUM([{variableDirectCostColumn.DataColumnId}])", + ColumnAlias = "Cost" + }); + } + + return values; + } + + private async Task> GetRows(List dataTables, List columns, CancellationToken cancellationToken) + { + var rows = new List(); + + foreach (var column in columns) + { + var dataColumn = await GetDataColumn(column, dataTables, cancellationToken); + if (dataColumn != null) + { + rows.Add(new Row() { ColumnAlias = column.ColumnAlias, DataColumnId = dataColumn.DataColumnId }); + } + } + + return rows; + } + + private static List GetColumns() + { + // added table aliases before column alias so that there is no ambiguity for columns from different tables + var columns = new List + { + // fpes + new StrategicColumn("EncounterId", "dsspes", "dsspesEncounterId"), + new StrategicColumn("DischargeDateTime", "dsspes", "dsspesDischargeDateTime"), + new StrategicColumn("EntityID", "dsspes", "dsspesEntityID"), + new StrategicColumn("PrimaryPhysicianId", "dsspes", "dsspesPhysicianId"), + new StrategicColumn("ServiceLineId", "dsspes", "dsspesPrimaryServiceLineId"), + new StrategicColumn("CPTCodeCaseTypeFamilyId", "dsspes", "dsspesCPTCodeCaseTypeFamilyId"), + new StrategicColumn("MSDRGCaseTypeFamilyId", "dsspes", "dsspesMSDRGCaseTypeFamilyId"), + new StrategicColumn("APRDRGCaseTypeFamilyId", "dsspes", "dsspesAPRDRGCaseTypeFamilyId"), + new StrategicColumn("PatientTypeRollupName", "PATIENTTYPE", "ptRollupName"), + + // pblid + new StrategicColumn("DepartmentId", "pblid", "pblidDepartmentId"), + + // dimphys + new StrategicColumn("PhysicianSpecialtyId", "physPrimary", "physPrimaryPhysicianSpecialtyId"), + + // chargecode + new StrategicColumn("ChargeCodeID", "cc", "ccChargeCodeID"), + new StrategicColumn("CostDriver", "cc", "ccCostDriver"), + +}; + + return columns; + } + + private async Task GetDataColumn(StrategicColumn strategicColumn, List dataTables, CancellationToken cancellationToken) + { + var schemaColumns = await dataSchemaService.GetDataColumnsBySqlColumnNameAsync(strategicColumn.ColumnName, cancellationToken); + var dataTable = dataTables.FirstOrDefault(dt => string.Equals(dt.SqlAlias, strategicColumn.TableAlias, StringComparison.OrdinalIgnoreCase)); + if (dataTable == null) return null; + var dataColumn = schemaColumns.FirstOrDefault(sc => sc.DataTableId == dataTable.DataTableId); + return dataColumn; + } + + private async Task GetDataSourceId(CancellationToken cancellationToken) + { + var dataSourceName = "DS Strata Encounter Cost Detail"; + var dataSources = await dataSchemaService.GetDataSourcesAsync(cancellationToken); + var matchingDataSources = dataSources + .Where(ds => string.Equals(ds.Name, dataSourceName, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (matchingDataSources.Count == 0) + { + throw new InvalidOperationException($"Required data source '{dataSourceName}' was not found."); + } + + if (matchingDataSources.Count > 1) + { + throw new InvalidOperationException($"Multiple data sources matched '{dataSourceName}'. Expected exactly one match."); + } + + return matchingDataSources[0].DataSourceId; + } + + private async Task> GetDataTablesForMeasure(StrategicOpportunityChargeCode strategicOpportunity, MeasureType measureType, int dataSourceId, CancellationToken cancellationToken) + { + var excludeTables = new List() + { + StrategicOpportunitySourceDbTable.DimPhysician + }; + + if (!(MeasureTypeConstants.PATIENT_COST_MEASURE_TYPES.Contains(measureType) || MeasureTypeConstants.MARGIN_MEASURE_TYPES.Contains(measureType))) + { + excludeTables.Add(StrategicOpportunitySourceDbTable.FactPatientCostDetail); + } + if (string.IsNullOrEmpty(strategicOpportunity.PatientPopulationHPath)) + { + excludeTables.Add(StrategicOpportunitySourceDbTable.FactPatientEncounterCLinicalIndicator); + } + if (strategicOpportunity.ExplorationPopulationId == 0) + { + excludeTables.Add(StrategicOpportunitySourceDbTable.FactExplorationPopulationEncounter); + } + + if (!ChipTypeExtensions.ContainsChips(strategicOpportunity.OpportunityFiltersJSON, [ChipKey.MSDRGRollupID])) + { + excludeTables.Add(StrategicOpportunitySourceDbTable.DimMsdrg); + } + + var tableList = StrategicOpportunitySourceDbTable.GetAllValues().Except(excludeTables); + var dataTables = await GetDataTables(tableList, dataSourceId, cancellationToken); + + return dataTables; + } + + private async Task> GetDataTablesForOpportunity(StrategicOpportunityChargeCode strategicOpportunity, bool isGetValues, int dataSourceId, CancellationToken cancellationToken) + { + var excludeTables = new List(); + + if (!isGetValues) + { + excludeTables.Add(StrategicOpportunitySourceDbTable.FactPatientCostDetail); + } + + if (string.IsNullOrEmpty(strategicOpportunity.PatientPopulationHPath)) + { + excludeTables.Add(StrategicOpportunitySourceDbTable.FactPatientEncounterCLinicalIndicator); + } + if (strategicOpportunity.ExplorationPopulationId == 0) + { + excludeTables.Add(StrategicOpportunitySourceDbTable.FactExplorationPopulationEncounter); + } + + if (!ChipTypeExtensions.ContainsChips(strategicOpportunity.OpportunityFiltersJSON, [ChipKey.MSDRGRollupID])) + { + excludeTables.Add(StrategicOpportunitySourceDbTable.DimMsdrg); + } + + var tableList = StrategicOpportunitySourceDbTable.GetAllValues().Except(excludeTables); + var dataTables = await GetDataTables(tableList, dataSourceId, cancellationToken); + + return dataTables; + } + + private async Task> GetDataTables(IEnumerable tables, int dataSourceId, CancellationToken cancellationToken) + { + var allTables = await dataSchemaService.GetDataTablesByDataSourceId(dataSourceId, false, null, cancellationToken); + var aliasesToInclude = new HashSet(tables.Select(t => t.SqlAlias), StringComparer.OrdinalIgnoreCase); + return allTables.Where(t => aliasesToInclude.Contains(t.SqlAlias)).ToList(); + } + + private Period GetProcessedDateRange(Period dateRange, StrategicOpportunityChargeCode opportunity, MeasureFilterDto measureFilterDto) + { + var dateStart = dateRange?.Start ?? + (measureFilterDto.FiltersForMeasure || !opportunity.IsOpportunity ? + opportunity.TrackingStartDate : + opportunity.BaselineStartDate); + + var dateEnd = dateRange?.End ?? + (measureFilterDto.FiltersForMeasure ? + DateOnly.FromDateTime(DateTime.UtcNow) : + (!opportunity.IsOpportunity ? opportunity.GetTrackingEndDateForFilters() : opportunity.BaselineEndDate)); + + return new Period(dateStart, dateEnd); + } + + private async Task<(List FiltersList, bool HaveNoIntersectedIds)> GetBaseFiltersAsync(StrategicOpportunityChargeCode opportunity, Period dateRange, + List dataTables, MeasureFilterDto measureFilterDto, CancellationToken cancellationToken) + { + var filterList = new List(); + + + var dischargeDateTimeColumn = await GetDataColumn(new StrategicColumn("DischargeDateTime", "dsspes", "DischargeDateTime"), dataTables, cancellationToken); + if (dischargeDateTimeColumn != null) + { + var dateStartCondition = new DataSchema.Models.Query.FilterCondition() + { + Operator = FilterOperator.GreaterThanOrEqualTo, + Values = [dateRange.Start.ToString(DateExtensions.OPPORTUNITY_FORMAT_MM_DD_YYYY)] + }; + var dateEndCondition = new DataSchema.Models.Query.FilterCondition() + { + Operator = FilterOperator.LessThan, + Values = [dateRange.End.AddDays(1).ToString(DateExtensions.OPPORTUNITY_FORMAT_MM_DD_YYYY)] + }; + + var dischargeDateFilter = new DataSchema.Models.Query.Filter() + { + DataColumnId = dischargeDateTimeColumn.DataColumnId, + FilterType = DataSchema.Models.Query.FilterType.Conditions, + Conditions = new List { dateStartCondition, dateEndCondition }, + DatePart = DatePart.Day + }; + filterList.Add(dischargeDateFilter); + } + + if (!string.IsNullOrEmpty(opportunity.PatientPopulationHPath)) + { + var dataColumn = await GetDataColumn(new StrategicColumn("ClinicalIndicatorId", "dsspeci", "ClinicalIndicatorId"), dataTables, cancellationToken); + if (dataColumn != null) + { + var dimension = await schemaServiceClient.GetDimensionByGlobalIdAsync("Clinical Indicator"); + var hierarchyPaths = opportunity.PatientPopulationHPath.Split(","); + foreach (var codeHpaths in hierarchyPaths.Batch(250)) + { + var members = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, codeHpaths, cancellationToken); + var ids = members.Select(x => x.Id).ToList(); + var filterCondition = new FilterCondition() { Operator = FilterOperator.In, Values = ids }; + var filter = new DataSchema.Models.Query.Filter() + { + DataColumnId = dataColumn.DataColumnId, + FilterType = FilterType.Conditions, + Conditions = new List { filterCondition } + }; + filterList.Add(filter); + } + } + } + else if (string.IsNullOrEmpty(opportunity.PatientPopulationHPath)) + { + var dataColumn = await GetDataColumn(new StrategicColumn("ExplorationPopulationId", "explorationPopulationEncounter", "ExplorationPopulationId"), dataTables, cancellationToken); + if (dataColumn != null) + { + var ids = new List() { opportunity.ExplorationPopulationId }; + var filterCondition = new DataSchema.Models.Query.FilterCondition() { Operator = FilterOperator.Equals, Values = ids }; + var filter = new DataSchema.Models.Query.Filter() + { + DataColumnId = dataColumn.DataColumnId, + FilterType = DataSchema.Models.Query.FilterType.Conditions, + Conditions = new List { filterCondition }, + }; + filterList.Add(filter); + } + } + bool haveNoIntersectedIds = false; + if ((opportunity.OpportunityFiltersJSON != string.Empty && opportunity.OpportunityFiltersJSON != "[]") || measureFilterDto.MeasureFilters.Any()) + { + var additionalFilters = JsonConvert.DeserializeObject>(opportunity.OpportunityFiltersJSON); + var sameFilters = new List(); + foreach (var measureFilter in measureFilterDto.MeasureFilters) + { + if (measureFilter.Key == ChipKey.ChargeCode) + { + continue; + } + var existingFilter = additionalFilters.FirstOrDefault(f => f.Key == measureFilter.Key); + if (existingFilter != null) + { + sameFilters.Add(measureFilter); + } + else + { + additionalFilters.Add(measureFilter); + } + } + + foreach (var filterChipItem in additionalFilters) + { + if (filterChipItem.Key == ChipKey.CaseTypeFamilyID) + { + var dimension = await schemaServiceClient.GetDimensionByGlobalIdAsync(GetGlobalIdByChipKey(filterChipItem.Key)); + var members = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, filterChipItem.Filters.Select(f => f.Id), cancellationToken); + var ids = await JoinSameFilters(measureFilterDto.MeasureFilters, sameFilters, filterChipItem, dimension, members.Select(m => m.Id), cancellationToken); + haveNoIntersectedIds = haveNoIntersectedIds || !ids.Any(); + var msDrgFilter = await GetCaseTypeFamilyFilter(filterChipItem, dataTables, ids, "MSDRGCaseTypeFamilyID", cancellationToken); + var aprDrgFilter = await GetCaseTypeFamilyFilter(filterChipItem, dataTables, ids, "APRDRGCaseTypeFamilyID", cancellationToken); + var cptFilter = await GetCaseTypeFamilyFilter(filterChipItem, dataTables, ids, "CPTCodeCaseTypeFamilyID", cancellationToken); + filterList.Add(new FilterGroup(new List { msDrgFilter, aprDrgFilter, cptFilter }, + filterChipItem.Operator == OperatorType.In ? LogicalOperator.Or : LogicalOperator.And)); + } + else + { + var stratColumn = GetColumnByChipKey(filterChipItem.Key); + var dataColumn = await GetDataColumn(stratColumn, dataTables, cancellationToken); + if (dataColumn != null) + { + var filterCondition = await GetFilterCondition(measureFilterDto.MeasureFilters, filterChipItem, sameFilters, cancellationToken); + haveNoIntersectedIds = haveNoIntersectedIds || !filterCondition.Values.Any(); + // encounter level measure filters are only used to determine which encounterIds have at least one details with the filters + // if there is at least one details that matches filters we will take all of the details to calculate costs + if (stratColumn.TableAlias == "pblid" && measureFilterDto.FiltersForMeasure && MeasureTypeConstants.IsEncounterLevelMeasure(measureFilterDto.MeasureType)) + { + measureFilterDto.MarginMeasureFilters.Add(stratColumn.ColumnName, filterCondition); + continue; + } + var filter = new DataSchema.Models.Query.Filter() + { + DataColumnId = dataColumn.DataColumnId, + FilterType = DataSchema.Models.Query.FilterType.Conditions, + Conditions = new List { filterCondition }, + }; + filterList.Add(filter); + } + } + } + } + + return (filterList, haveNoIntersectedIds); + } + + private async Task> JoinSameFilters(List measureFilters, List sameFilters, FilterChipItem filterChipItem, Schema.Client.Dtos.Info.ScoreDimensionInfoDto dimension, IEnumerable ids, CancellationToken cancellationToken) + { + if (sameFilters.Any(f => f.Key == filterChipItem.Key)) + { + var measureMembers = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, + measureFilters.FirstOrDefault(f => f.Key == filterChipItem.Key).Filters.Select(f => f.Id), cancellationToken); + var measureFilter = sameFilters.FirstOrDefault(f => f.Key == filterChipItem.Key); + if (filterChipItem.Operator == OperatorType.In && measureFilter.Operator == OperatorType.In) + { + ids = ids.Intersect(measureMembers.Select(m => m.Id)); + } + else if (filterChipItem.Operator == OperatorType.NotIn && measureFilter.Operator == OperatorType.NotIn) + { + ids = ids.Union(measureMembers.Select(m => m.Id)); + } + else if (filterChipItem.Operator == OperatorType.In && measureFilter.Operator == OperatorType.NotIn) + { + ids = ids.Except(measureMembers.Select(m => m.Id)); + } + else + { + filterChipItem.Operator = OperatorType.In; + ids = measureMembers.Select(m => m.Id).Except(ids); + } + } + + return ids; + } + + private async Task IsUsingAprdrgAsync(CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + var ctfValueSetting = (await dbContext.SystemSettings.SingleOrDefaultAsync(item => item.Name == "Flexible Exploration Inpatient Case Type Family", cancellationToken))?.Value; + return ctfValueSetting == "1"; + } + + private async Task GetFilterCondition(List measureFilters, FilterChipItem filterChipItem, List sameFilters, CancellationToken cancellationToken) + { + var ids = new List(); + var oper = filterChipItem.Key switch + { + // 1 is an id for Yes and 0 is for No + ChipKey.IsGreaterThanZeroCharges => filterChipItem.Filters.Any(f => f.Id == "1") ? FilterOperator.GreaterThan : FilterOperator.LessThanOrEqualTo, + ChipKey.Mortality => filterChipItem.Filters.Any(f => f.Id == "1") ? FilterOperator.In : FilterOperator.NotIn, + _ => filterChipItem.Operator == OperatorType.In ? FilterOperator.In : FilterOperator.NotIn + }; + var hasIntersectedFilters = measureFilters.Any(mf => mf.ChipType == filterChipItem.ChipType && + (mf.Filters.Intersect(filterChipItem.Filters).Any() || mf.Operator != filterChipItem.Operator)); + + if (filterChipItem.Key == ChipKey.Mortality && ((sameFilters.Any(f => f.Key == filterChipItem.Key) && hasIntersectedFilters) + || !sameFilters.Any(f => f.Key == filterChipItem.Key))) + { + ids.AddRange(new List() { 20, 41, 42, 43 }); + } + else if (filterChipItem.Key == ChipKey.IsGreaterThanZeroCharges && ((sameFilters.Any(f => f.Key == filterChipItem.Key) && hasIntersectedFilters) + || !sameFilters.Any(f => f.Key == filterChipItem.Key))) + { + ids.Add(0); + } + else if (filterChipItem.Key == ChipKey.Supply) + { + if (sameFilters.Any(f => f.Key == filterChipItem.Key)) + { + var measureFilter = sameFilters.FirstOrDefault(f => f.Key == filterChipItem.Key); + if (filterChipItem.Operator == measureFilter.Operator) + { + ids.AddRange(filterChipItem.Filters.Select(f => f.Id).Intersect(measureFilter.Filters.Select(f => f.Id))); + } + else if (filterChipItem.Operator == OperatorType.In && measureFilter.Operator == OperatorType.NotIn) + { + ids.AddRange(filterChipItem.Filters.Select(f => f.Id).Except(measureFilter.Filters.Select(f => f.Id))); + } + else + { + filterChipItem.Operator = OperatorType.In; + ids.AddRange(measureFilter.Filters.Select(f => f.Id).Except(filterChipItem.Filters.Select(f => f.Id))); + } + } + else + { + ids.AddRange(filterChipItem.Filters.Select(f => f.Id)); + } + } + else + { + var dimension = await schemaServiceClient.GetDimensionByGlobalIdAsync(GetGlobalIdByChipKey(filterChipItem.Key)); + var members = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, filterChipItem.Filters.Select(f => f.Id), cancellationToken); + ids.AddRange(await JoinSameFilters(measureFilters, sameFilters, filterChipItem, dimension, members.Select(m => m.Id), cancellationToken)); + } + + return new FilterCondition() + { + Operator = oper, + Values = ids + }; + } + + private async Task GetCaseTypeFamilyFilter(FilterChipItem filterChipItem, List dataTables, + IEnumerable ids, string columnName, CancellationToken cancellationToken) + { + var dataColumn = await GetDataColumn(new StrategicColumn(columnName, "dsspes", columnName), dataTables, cancellationToken); + var filter = new DataSchema.Models.Query.Filter() + { + DataColumnId = dataColumn.DataColumnId, + FilterType = DataSchema.Models.Query.FilterType.Conditions, + Conditions = new List { new FilterCondition() + { + Operator = filterChipItem.Operator == OperatorType.In ? FilterOperator.In : FilterOperator.NotIn, + Values = ids + } } + }; + return filter; + } + + private string GetGlobalIdByChipKey(ChipKey key) + { + return key switch + { + ChipKey.AdmitSourceID => "Admit Source", + ChipKey.AdmitTypeID => "Admit Type", + ChipKey.AgeCohortID => "Age Cohort", + ChipKey.AprDrgID => "APR DRG", + ChipKey.CaseTypeFamilyID => "Case Type Family", + ChipKey.DischargeStatusId => "Discharge Status", + ChipKey.EntityID => "DSS Entity", + ChipKey.EncounterID => "Patient Encounter", + ChipKey.GenderID => "Gender", + ChipKey.ICD10DXPrimaryDiagID => "ICD10 DX Primary", + ChipKey.ICD10PXPrimaryID => "ICD10 PX Primary", + ChipKey.InsurancePlan1ID => "Insurance Plan 1", + ChipKey.LocationID => "Location", + ChipKey.MSDRGRollupID => "MS DRG Rollup", + ChipKey.NewBornFlagID => "New Born Flag", + ChipKey.PatientTypeID => "Patient Type Rollup", + ChipKey.PrimaryCPTID => "Primary CPT", + ChipKey.PrimaryPhysicianID => "Physician Primary", + ChipKey.PrimaryQVIID => "Primary QVI", + ChipKey.SourceSystemID => "Source System", + ChipKey.BilledCPT => "Billed CPT", + ChipKey.Supply => "DSS Supply", + ChipKey.UBRevenueCode => "UB Revenue Code", + ChipKey.Department => "Department", + _ => null + }; + } + + private StrategicColumn GetColumnByChipKey(ChipKey key) + { + return key switch + { + ChipKey.AdmitSourceID => new StrategicColumn("AdmitSourceId", "dsspes", "AdmitSourceId"), + ChipKey.AdmitTypeID => new StrategicColumn("AdmitTypeId", "dsspes", "AdmitTypeId"), + ChipKey.AgeCohortID => new StrategicColumn("AgeCohortID", "dsspes", "AgeCohortID"), + ChipKey.AprDrgID => new StrategicColumn("AprdrgId", "dsspes", "AprdrgId"), + ChipKey.DischargeStatusId => new StrategicColumn("DischargeStatusId", "dsspes", "DischargeStatusId"), + ChipKey.EntityID => new StrategicColumn("EntityId", "dsspes", "EntityId"), + ChipKey.EncounterID => new StrategicColumn("EncounterId", "dsspes", "EncounterId"), + ChipKey.GenderID => new StrategicColumn("GenderId", "dsspes", "GenderId"), + ChipKey.ICD10DXPrimaryDiagID => new StrategicColumn("ICD10DXPrimaryDiagId", "dsspes", "ICD10DXPrimaryDiagId"), + ChipKey.ICD10PXPrimaryID => new StrategicColumn("ICD10PXPrimaryId", "dsspes", "ICD10PXPrimaryId"), + ChipKey.InsurancePlan1ID => new StrategicColumn("InsurancePlan1Id", "dsspes", "InsurancePlan1Id"), + ChipKey.IsGreaterThanZeroCharges => new StrategicColumn("TotalCharges", "dsspes", "TotalCharges"), + ChipKey.LocationID => new StrategicColumn("LocationId", "dsspes", "LocationId"), + ChipKey.Mortality => new StrategicColumn("DischargeStatusId", "dsspes", "DischargeStatusId"), + ChipKey.NewBornFlagID => new StrategicColumn("NewBornFlagId", "dsspes", "NewBornFlagId"), + ChipKey.PatientTypeID => new StrategicColumn("PatientTypeId", "dsspes", "PatientTypeId"), + ChipKey.PrimaryCPTID => new StrategicColumn("PrimaryCPTId", "dsspes", "PrimaryCPTId"), + ChipKey.PrimaryPhysicianID => new StrategicColumn("PrimaryPhysicianId", "dsspes", "PrimaryPhysicianId"), + ChipKey.PrimaryQVIID => new StrategicColumn("PrimaryQVIId", "dsspes", "PrimaryQVIId"), + ChipKey.SourceSystemID => new StrategicColumn("SourceSystemId", "dsspes", "SourceSystemId"), + ChipKey.BilledCPT => new StrategicColumn("BilledCPTId", "pblid", "BilledCPTId"), + ChipKey.Supply => new StrategicColumn("SupplyId", "pblid", "SupplyId"), + ChipKey.UBRevenueCode => new StrategicColumn("UBRevenueCodeId", "pblid", "UBRevenueCodeId"), + ChipKey.Department => new StrategicColumn("DepartmentId", "pblid", "DepartmentId"), + ChipKey.MSDRGRollupID => new StrategicColumn("msdrgRollupId", "msdrg", "msdrgRollupId"), + ChipKey.ChargeCode => new StrategicColumn("ChargeCodeId", "cc", "ChargeCodeId"), + _ => null + }; + } + + private async Task> GetMeasureValues(MeasureType measure, List dataTables, CancellationToken cancellationToken) + { + var values = new List(); + if (measure != MeasureType.EncountersCases) + { + var column = measure switch + { + MeasureType.UnitsOfService or MeasureType.UnitsOfServicePerCase => new StrategicColumn("UnitsOfService", "pblid", "UnitsOfService"), + MeasureType.DirectVariableCost or MeasureType.DirectVariableCostPerCase + or MeasureType.DirectVariableCostPerUnit => new StrategicColumn("VariableDirectCost", "costdetail", "VariableDirectCost"), + MeasureType.TotalDirectCost or MeasureType.TotalDirectCostPerCase + or MeasureType.TotalDirectCostPerUnit => new StrategicColumn("DirectCost", "costdetail", "DirectCost"), + MeasureType.TotalCharges or MeasureType.TotalChargesPerCase + or MeasureType.TotalChargesPerUnit => new StrategicColumn("Charge", "pblid", "Charge"), + MeasureType.AcquisitionSupplyCost or MeasureType.AcquisitionSupplyCostPerCase + or MeasureType.AcquisitionSupplyCostPerUnit => new StrategicColumn("SupplyTotalCost", "pblid", "SupplyTotalCost"), + MeasureType.Days => new StrategicColumn("Days", "pblid", "Days"), + MeasureType.AverageLengthOfStay => new StrategicColumn("LengthOfStay", "dsspes", "LengthOfStay"), + _ => null + }; + // add margin measure + if (column != null) + { + var dataColumn = await GetDataColumn(column, dataTables, cancellationToken); + if (dataColumn != null) + { + values.Add(new QueryValue() + { + Calculation = $"SUM([{dataColumn.DataColumnId}])", + ColumnAlias = "Value" + }); + } + } + if (MeasureTypeConstants.PER_UNIT_MEASURE_TYPES.Contains(measure)) + { + var unitsOfService = await GetDataColumn(new StrategicColumn("UnitsOfService", "pblid", "UnitsOfService"), dataTables, cancellationToken); + if (unitsOfService != null) + { + values.Add(new QueryValue() + { + Calculation = $"SUM([{unitsOfService.DataColumnId}])", + ColumnAlias = "UnitsOfService" + }); + } + } + if (MeasureTypeConstants.MARGIN_MEASURE_TYPES.Contains(measure)) + { + var variableDirectCost = await GetDataColumn(new StrategicColumn("VariableDirectCost", "costdetail", "VariableDirectCost"), dataTables, cancellationToken); + switch (measure) + { + case MeasureType.ContributionMargin: + case MeasureType.ContributionMarginPerCase: + { + if (variableDirectCost != null) + { + values.Add(new QueryValue() + { + Calculation = $"SUM([{variableDirectCost.DataColumnId}])", + ColumnAlias = "Value" + }); + } + break; + } + case MeasureType.DirectMargin: + case MeasureType.DirectMarginPerCase: + { + var fixedDirectCost = await GetDataColumn(new StrategicColumn("FixedDirectCost", "costdetail", "FixedDirectCost"), dataTables, cancellationToken); + + if (variableDirectCost != null && fixedDirectCost != null) + { + values.Add(new QueryValue() + { + Calculation = $"SUM([{variableDirectCost.DataColumnId}] + [{fixedDirectCost.DataColumnId}])", + ColumnAlias = "Value" + }); + } + break; + } + case MeasureType.TotalMargin: + case MeasureType.MarginPerCase: + { + var variableIndirectCost = await GetDataColumn(new StrategicColumn("VariableIndirectCost", "costdetail", "VariableIndirectCost"), dataTables, cancellationToken); + var fixedDirectCost = await GetDataColumn(new StrategicColumn("FixedDirectCost", "costdetail", "FixedDirectCost"), dataTables, cancellationToken); + var fixedIndirectCost = await GetDataColumn(new StrategicColumn("FixedIndirectCost", "costdetail", "FixedIndirectCost"), dataTables, cancellationToken); + + if (variableDirectCost != null && variableIndirectCost != null && fixedDirectCost != null && fixedIndirectCost != null) + { + values.Add(new QueryValue() + { + Calculation = $"SUM([{variableDirectCost.DataColumnId}] + [{variableIndirectCost.DataColumnId}] + [{fixedDirectCost.DataColumnId}] + [{fixedIndirectCost.DataColumnId}])", + ColumnAlias = "Value" + }); + } + break; + } + case MeasureType.VariableMargin: + case MeasureType.VariableMarginPerCase: + { + var variableIndirectCost = await GetDataColumn(new StrategicColumn("VariableIndirectCost", "costdetail", "VariableIndirectCost"), dataTables, cancellationToken); + + if (variableDirectCost != null && variableIndirectCost != null) + { + values.Add(new QueryValue() + { + Calculation = $"SUM([{variableDirectCost.DataColumnId}] + [{variableIndirectCost.DataColumnId}])", + ColumnAlias = "Value" + }); + } + break; + } + } + } + } + + return values; + } + + private async Task UpdateLastRunTracking(StrategicOpportunityChargeCode opportunity, RunTrackingStatus status, CancellationToken cancellationToken) + { + opportunity.LastRunTrackingDate = DateTime.UtcNow; + opportunity.LastRunTrackingStatus = status; + _centralDbContext.StrategicOpportunityChargeCodes.Update(opportunity); + await _centralDbContext.SaveChangesAsync(cancellationToken); + await strategicItemDimensionSyncService.UpsertAsync(opportunity, cancellationToken); + } + + private async Task PopulateTransientChargeCodeIds(IEnumerable idsList, long opportunityId, CancellationToken cancellationToken) + { + var sql = $@" + CREATE OR REPLACE TRANSIENT TABLE cci.StrategicChargeCodeIds_{opportunityId} (ChargeCodeId INTEGER) AS + {GetSqlSelectChargeCodeIds(idsList, false)} + "; + + await databaseContext.ExecuteCommandAsync(sql); + } + + private string GetSqlSelectChargeCodeIds(IEnumerable idsList, bool withCte) + { + var invalidId = idsList.FirstOrDefault(x => !long.TryParse(x, out _)); + if (invalidId != default) + throw new InvalidOperationException($"Invalid ChargeCodeId: {invalidId}"); + + var selectSql = $@" + SELECT * + FROM ( + VALUES {string.Join(",", idsList.Select(id => $"({id})"))} + ) + "; + + return withCte ? $@" + WITH StrategicChargeCodeIds_CTE (ChargeCodeId) AS ( + {selectSql} + ) + " : selectSql; + } + + private async Task> GetChargeCodeIds(string chargeCodeHPath, CancellationToken cancellationToken) + { + var idsList = new List(); + var chargeCodeDimension = await schemaServiceClient.GetDimensionByGlobalIdAsync("Charge Code"); + var hierarchyPaths = chargeCodeHPath.Split(","); + foreach (var batch in hierarchyPaths.Batch(250)) + { + var members = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(chargeCodeDimension.DimensionGuid, batch, cancellationToken); + idsList.AddRange(members.Select(x => x.Id)); + } + + if (idsList.Count == 0) + { + throw new InvalidOperationException("No Charge Codes found for the provided hierarchy paths."); + } + + return idsList; + } + + private async Task CleanupTransientChargeCodeIds(StrategicOpportunityChargeCode opportunity) + { + var dropSql = $"DROP TABLE IF EXISTS cci.StrategicChargeCodeIds_{opportunity.OpportunityId};"; + await databaseContext.ExecuteCommandAsync(dropSql); + } + + private string ApplyExplorationFilters(StrategicOpportunityChargeCode opportunity, string selectQuery, + List> parameters, string ctfSummaryQuery) + { + selectQuery = System.Text.RegularExpressions.Regex.Replace( + selectQuery, + @"(INNER|LEFT|RIGHT)\s+JOIN\s+CCI\.FACTEXPLORATIONPOPULATIONENCOUNTER", + "INNER JOIN ( " + ctfSummaryQuery + " ) ", + System.Text.RegularExpressions.RegexOptions.IgnoreCase); + selectQuery = selectQuery.Replace( + "EXPLORATIONPOPULATIONENCOUNTER.EncounterId", + "EXPLORATIONPOPULATIONENCOUNTER.\"FilterEncounterId\"", + StringComparison.CurrentCultureIgnoreCase + ); + selectQuery = selectQuery.Replace( + "EXPLORATIONPOPULATIONENCOUNTER.ExplorationPopulationId", + opportunity.ExplorationPopulationId.ToString(), + StringComparison.CurrentCultureIgnoreCase + ); + return selectQuery; + } + + private async Task<(QueryParams queryParams, string ctfSummaryQuery)> GetExplorationQuery(long explorationPopulationId, DateOnly startDate, DateOnly endDate, CancellationToken cancellationToken) + { + var isUsingAprdrg = await IsUsingAprdrgAsync(cancellationToken); + var exploration = await centralDbContext.ExplorationPopulations.FirstOrDefaultAsync(e => e.ExplorationPopulationId == explorationPopulationId, cancellationToken); + + var queryParams = new QueryParams + { + CodeType = isUsingAprdrg ? CaseTypeCategories.APRDRG : CaseTypeCategories.MSDRG, + CaseTypeFamilyId = exploration.CaseTypeFamilyId + }; + + var expFilters = exploration.FilterChipItems; + var dateFilters = expFilters.FirstOrDefault(f => f.ChipType == ChipType.DateRange); + dateFilters.DateRange = new List + { + startDate.ToDateTime(TimeOnly.MinValue), + // Info: Don't need to add 1 day to end date here like we do in other places because the exploration filter service will handle date range as inclusive and add 1 day to end date internally + endDate.ToDateTime(TimeOnly.MinValue) + }; + var filters = await explorationFilterService.GetFilterStringsAsync(expFilters, queryParams, cancellationToken); + var ctfSummaryQuery = ExplorationServiceQuery.BuildCaseTypeFamilySummaryEncounterQuery(filters.QueryFilters, filters.EncounterSummaryFilters); + return (queryParams, ctfSummaryQuery); + } + + private async Task UpdateSystemMeasures(StrategicOpportunityChargeCode opportunity, MeasureType measureType, CancellationToken cancellationToken) + { + var measure = await _centralDbContext.StrategicOpportunityMeasures.FirstOrDefaultAsync(m => m.OpportunityId == opportunity.OpportunityId && m.StrataId == _user.GetStrataId() && m.MeasureGroup == Enums.MeasureGroup.System && m.MeasureType == measureType, cancellationToken); + var trackingMonths = DateExtensions.GetMonthDifference(opportunity.TrackingStartDate, measureType == MeasureType.ActualCost ? DateOnly.FromDateTime(opportunity.LastRunTrackingDate!.Value) : opportunity.TrackingEndDate, true); + + if (measure != null) + { + var query = strategicOpportunityDetailQueryBuilder.BuildMergeSystemMeasureMergeQuery(opportunity.OpportunityId, measure.MeasureId, measureType, opportunity.TrackingStartDate.ToString(DateExtensions.MEASURE_FORMAT_YYYY_MM_DD), trackingMonths); + await databaseContext.ExecuteCommandAsync(query); + } + } + + private class MeasureFilterDto + { + internal List MeasureFilters { get; set; } + internal bool FiltersForMeasure { get; set; } + internal MeasureType MeasureType { get; set; } + internal Dictionary MarginMeasureFilters { get; set; } = new Dictionary(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicColumn.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicColumn.cs new file mode 100644 index 0000000..b815565 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicColumn.cs @@ -0,0 +1,17 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicColumn + { + public StrategicColumn(string columnName, string tableAlias, string columnAlias) + { + ColumnName = columnName; + TableAlias = tableAlias; + ColumnAlias = columnAlias; + } + + public string ColumnName { get; set; } + public string TableAlias { get; set; } + public string ColumnAlias { get; set; } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicGLOpportunityDetailService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicGLOpportunityDetailService.cs new file mode 100644 index 0000000..72f87b1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicGLOpportunityDetailService.cs @@ -0,0 +1,306 @@ +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.Schema.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicGLOpportunityDetailService( + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + ISchemaServiceClient schemaServiceClient, + ISnowflakeDatabaseContext databaseContext, + IGLDataSourceStrategy glDataSourceStrategy, + IDistributionProcessFactory distributionProcessFactory, + IHubContext notificationHubContext, + IStrategicItemDimensionSyncService strategicItemDimensionSyncService, + ILogger logger = default) : IStrategicGLOpportunityDetailService + { + private readonly CentralDbContext _centralDbContext = centralDbContext; + private readonly ClaimsPrincipal _user = claimsPrincipalAccessor?.GetCurrentClaimsPrincipal(); + public async Task GetStrategicOpportunityGLDetailData(long opportunityId, bool isTracking, string[] strDimensions, CancellationToken cancellationToken) + { + var dimensions = strDimensions.Select(x => StrategicOpportunityDetailDimension.GetFromString(x)).ToList(); + if (dimensions.Any(x => x == null)) return null; + + var dicDimensionSql = StrategicOpportunityGLServiceQuery.GetDetailQuery(dimensions, false); + var queryCount = dicDimensionSql.Count; + if (queryCount == 0) return null; + var querySelect = string.Join(Environment.NewLine, dicDimensionSql.Values); + + var strategicOpportunity = await _centralDbContext.StrategicOpportunities.FirstOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + if (strategicOpportunity == null) return null; + + // NOTE: @IsBaseline is substituted as a literal string, not a bound parameter. + // When isTracking=false, "NULL" is injected so the SQL short-circuit + // (@IsBaseline IS NULL OR IsBaseline = @IsBaseline) returns all rows. + // Any new dimension SQL file using @IsBaseline MUST follow this same pattern. + var parametersReplace = new Dictionary + { + { "@OpportunityId", opportunityId.ToString() }, + { "@IsBaseline", isTracking ? "False" : "NULL" } + }; + foreach (var x in parametersReplace) + { + querySelect = querySelect.Replace(x.Key, x.Value); + } + + var queryOneParams = new Dictionary + { + { "MULTI_STATEMENT_COUNT", queryCount } + }; + + var data = new StrategicOpportunityGLDetailData() + { + DimensionsData = dimensions.ToDictionary(x => x.ToString(), x => new Dictionary()), + Data = [] + }; + + try + { + var results = await databaseContext.QueryMultipleAsync(querySelect, "GL Entity Detail", queryOneParams); + foreach (var strDimension in dicDimensionSql.Keys.Where(data.DimensionsData.ContainsKey)) + { + var dimension = StrategicOpportunityDetailDimension.GetFromString(strDimension); + var isStringKey = StrategicOpportunityService.GLDimensionsConfig.TryGetValue(dimension, out var dimConfig) && dimConfig.IsKeyString; + data.DimensionsData[strDimension] = isStringKey + ? results.Read>().ToDictionary(x => x.Key, x => x.Value) + : results.Read>().ToDictionary(x => x.Key.ToString(), x => x.Value); + } + + data.Data = results.Read(); + } + catch (Exception ex) + { + logger.LogServiceError(ex, info: new Dictionary { { "query", querySelect }, { "queryParams", queryOneParams } }); + throw; + } + + return data; + } + + public async Task PopulateStrategicOpportunityGLDetails(StrategicOpportunityGL opportunity, CancellationToken cancellationToken) + { + var departmentIds = await ResolveDimensionIds("Department", opportunity.DepartmentHPath, cancellationToken); + var (revenueAccountIds, expenseAccountIds, statisticAccountIds) = await ResolveAccountIdsByType(opportunity, cancellationToken); + var sourceTable = await glDataSourceStrategy.GetGLFactTableAsync(); + + var queryBuilder = new StrategicOpportunityGLDetailQueryBuilder(); + var deleteSql = queryBuilder.BuildDeleteQuery(opportunity.OpportunityId); + var insertSql = queryBuilder.BuildInsertQuery(opportunity.OpportunityId, sourceTable, departmentIds, revenueAccountIds, expenseAccountIds, statisticAccountIds, opportunity.BaselineStartDate, opportunity.BaselineEndDate); + + await databaseContext.QueryAsync(deleteSql, $"{nameof(PopulateStrategicOpportunityGLDetails)}.Delete"); + await databaseContext.QueryAsync(insertSql, $"{nameof(PopulateStrategicOpportunityGLDetails)}.Insert"); + + await PopulateStrategicOpportunityGLGoalDetails(opportunity, DistributionStage.FromSourceToOpportunity, true, cancellationToken); + } + + public async Task PopulateStrategicOpportunityGLGoalDetails(StrategicOpportunityGL opportunity, DistributionStage stage, bool isSaveChanges, CancellationToken cancellationToken) + { + var rampUps = await _centralDbContext.StrategicOpportunityRampUps + .Where(x => x.OpportunityId == opportunity.OpportunityId) + .OrderBy(x => x.MonthNumber) + .Select(x => x.Percentage) + .ToArrayAsync(cancellationToken); + + var distributionProcessGL = distributionProcessFactory.CreateDistributionProcessGL( + opportunity.OpportunityId, + new Shared.Period(opportunity.BaselineStartDate, opportunity.BaselineEndDate), + new Shared.Period(opportunity.BaselineEndDate.AddMonths(1), opportunity.BaselineEndDate.AddMonths(opportunity.EstimatedTrackingDuration)), + new Shared.Period(opportunity.TrackingStartDate, opportunity.TrackingEndDate), + rampUps + ); + + var distributionSummary = await distributionProcessGL.DistributeAsync(opportunity.BaselineDistribution, stage); + opportunity.TotalImprovement = distributionSummary.ImprovementSum; + opportunity.EstimatedGoal = distributionSummary.EstimatedGoalSum; + + if (isSaveChanges) await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + public async Task PopulateGLDetailGoalSummary(StrategicOpportunityGL opportunity, double[] rampUps, bool isSaveChanges, CancellationToken cancellationToken) + { + if (rampUps == null) + { + rampUps = await _centralDbContext.StrategicOpportunityRampUps + .Where(x => x.OpportunityId == opportunity.OpportunityId) + .OrderBy(x => x.MonthNumber) + .Select(x => x.Percentage) + .ToArrayAsync(cancellationToken); + } + + var distributionProcessGL = distributionProcessFactory.CreateDistributionProcessGL( + opportunity.OpportunityId, + new Shared.Period(opportunity.BaselineStartDate, opportunity.BaselineEndDate), + new Shared.Period(opportunity.BaselineEndDate.AddMonths(1), opportunity.BaselineEndDate.AddMonths(opportunity.EstimatedTrackingDuration)), + new Shared.Period(opportunity.TrackingStartDate, opportunity.TrackingEndDate), + rampUps + ); + + var distributionSummary = await distributionProcessGL.GetDistributionSummaryAsync(false); + opportunity.TotalImprovement = distributionSummary.ImprovementSum; + opportunity.EstimatedGoal = distributionSummary.EstimatedGoalSum; + + if (isSaveChanges) await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + public async Task PopulateStrategicOpportunityCommittedDetails(StrategicOpportunityGL opportunity, DistributionStage stage, bool isSaveChanges, CancellationToken cancellationToken) + { + var rampUps = await _centralDbContext.StrategicOpportunityRampUps + .Where(x => x.OpportunityId == opportunity.OpportunityId) + .OrderBy(x => x.MonthNumber) + .Select(x => x.Percentage) + .ToArrayAsync(cancellationToken); + + var distributionProcessGL = distributionProcessFactory.CreateDistributionProcessGL( + opportunity.OpportunityId, + new Shared.Period(opportunity.BaselineStartDate, opportunity.BaselineEndDate), + new Shared.Period(opportunity.BaselineEndDate.AddMonths(1), opportunity.BaselineEndDate.AddMonths(opportunity.EstimatedTrackingDuration)), + new Shared.Period(opportunity.TrackingStartDate, opportunity.TrackingEndDate), + rampUps + ); + + var distributionSummary = await distributionProcessGL.DistributeAsync(opportunity.BaselineDistribution, stage); + opportunity.TotalCommittedCost = distributionSummary.CommittedSum; + + if (isSaveChanges) await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateDetailCommittedByRampUp(StrategicOpportunityGL opportunity, double[] oldRampUps, double[] newRampUps, bool isSaveChanges, CancellationToken cancellationToken) + { + var distributionProcessGL = distributionProcessFactory.CreateDistributionProcessGL( + opportunity.OpportunityId, + new Shared.Period(opportunity.BaselineStartDate, opportunity.BaselineEndDate), + new Shared.Period(opportunity.BaselineEndDate.AddMonths(1), opportunity.BaselineEndDate.AddMonths(opportunity.EstimatedTrackingDuration)), + new Shared.Period(opportunity.TrackingStartDate, opportunity.TrackingEndDate), + newRampUps + ); + + var distributionSummary = await distributionProcessGL.UpdateDetailCommittedByRampUp(oldRampUps); + opportunity.TotalCommittedCost = distributionSummary.CommittedSum; + + if (isSaveChanges) await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + public async Task UpdateStrategicOpportunityGLDetailsData(long opportunityId, IEnumerable adjustments, IEnumerable rows, CancellationToken cancellationToken) + { + try + { + var opportunity = await _centralDbContext.StrategicOpportunityGLs.SingleOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + if (opportunity == null) return false; + + var queryBuilder = new StrategicOpportunityGLUpdateQueryBuilder(); + var (updateQuery, parameters) = queryBuilder.BuildUpdateQuery(adjustments, opportunityId, rows); + await databaseContext.QueryAsync(updateQuery, nameof(UpdateStrategicOpportunityGLDetailsData), parameters); + + opportunity.Status = (byte)StrategicOpportunityStatus.InValidation; + + await PopulateGLDetailGoalSummary(opportunity, null, false, cancellationToken); + + await _centralDbContext.SaveChangesAsync(cancellationToken); + await strategicItemDimensionSyncService.UpsertAsync(opportunity, cancellationToken); + + return true; + } + catch (Exception ex) + { + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + public async Task PopulateTrackingDetails(StrategicOpportunityGL opportunity, CancellationToken cancellationToken) + { + var hub = notificationHubContext.Clients.Group(opportunity.OpportunityId.ToString()); + + try + { + if (opportunity.Status == (byte)StrategicOpportunityStatus.Tracking) + { + var departmentIds = await ResolveDimensionIds("Department", opportunity.DepartmentHPath, cancellationToken); + var (revenueAccountIds, expenseAccountIds, statisticAccountIds) = await ResolveAccountIdsByType(opportunity, cancellationToken); + var sourceTable = await glDataSourceStrategy.GetGLFactTableAsync(); + + var queryBuilder = new StrategicOpportunityGLDetailQueryBuilder(); + var mergeSql = queryBuilder.BuildMergeQuery(opportunity.OpportunityId, sourceTable, departmentIds, revenueAccountIds, expenseAccountIds, statisticAccountIds, opportunity.TrackingStartDate, opportunity.TrackingEndDate); + await databaseContext.QueryAsync(mergeSql, $"{nameof(PopulateTrackingDetails)}.Merge"); + + var actualCost = await databaseContext.ExecuteScalarAsync($"SELECT COALESCE(SUM(Amount), 0) FROM {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} WHERE IsBaseline = FALSE AND OpportunityId = {opportunity.OpportunityId}"); + var committedCost = await databaseContext.ExecuteScalarAsync(@$"SELECT COALESCE(SUM(GoalAmount), 0) FROM {StrategicOpportunityService.StrategicOpportunityGLDetailTableName} WHERE IsBaseline = FALSE + AND (FiscalYearId < {DateTime.UtcNow.Year} OR (FiscalYearId = {DateTime.UtcNow.Year} AND FiscalMonthId <= {DateTime.UtcNow.Month})) AND OpportunityId = {opportunity.OpportunityId}"); + + opportunity.ProgressToDateActual = actualCost; + opportunity.ProgressToDateCommitted = committedCost; + await _centralDbContext.SaveChangesAsync(cancellationToken); + } + + await UpdateLastRunTracking(opportunity, RunTrackingStatus.Success, cancellationToken); + await hub.SendStrategicInitiativeTrackerSuccess(opportunity.OpportunityId.ToString(), (byte)opportunity.LastRunTrackingStatus, opportunity.LastRunTrackingDate); + } + catch (Exception ex) + { + await UpdateLastRunTracking(opportunity, RunTrackingStatus.Failed, cancellationToken); + await hub.SendStrategicInitiativeTrackerError(opportunity.OpportunityId.ToString(), (byte)opportunity.LastRunTrackingStatus, opportunity.LastRunTrackingDate); + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + private async Task> ResolveDimensionIds(string globalId, string hPath, CancellationToken cancellationToken) + { + var idsList = new List(); + if (string.IsNullOrEmpty(hPath)) return idsList; + + var dimension = await schemaServiceClient.GetDimensionByGlobalIdAsync(globalId); + var hierarchyPaths = hPath.Split(","); + foreach (var batch in hierarchyPaths.Batch(250)) + { + var members = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, batch, cancellationToken); + idsList.AddRange(members.Select(x => int.Parse(x.Id))); + } + return idsList; + } + + private async Task<(List Revenue, List Expense, List Statistic)> ResolveAccountIdsByType(StrategicOpportunityGL opportunity, CancellationToken cancellationToken) + { + var revenueIds = string.IsNullOrEmpty(opportunity.RevenueAccountHPath) + ? [] + : await ResolveDimensionIds("Account", opportunity.RevenueAccountHPath, cancellationToken); + + var expenseIds = string.IsNullOrEmpty(opportunity.ExpenseAccountHPath) + ? [] + : await ResolveDimensionIds("Account", opportunity.ExpenseAccountHPath, cancellationToken); + + var statisticIds = opportunity.HasStatistic && !string.IsNullOrEmpty(opportunity.StatisticAccountHPath) + ? await ResolveDimensionIds("Account", opportunity.StatisticAccountHPath, cancellationToken) + : []; + + return (revenueIds, expenseIds, statisticIds); + } + + private async Task UpdateLastRunTracking(StrategicOpportunity opportunity, RunTrackingStatus status, CancellationToken cancellationToken) + { + opportunity.LastRunTrackingDate = DateTime.UtcNow; + opportunity.LastRunTrackingStatus = status; + await _centralDbContext.SaveChangesAsync(cancellationToken); + await strategicItemDimensionSyncService.UpsertAsync(opportunity, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicInitiativeChargeCodeExportService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicInitiativeChargeCodeExportService.cs new file mode 100644 index 0000000..29f743d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicInitiativeChargeCodeExportService.cs @@ -0,0 +1,213 @@ +using ClosedXML.Excel; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Data; +using System.Linq; +using static Strata.ContinuousImprovement.Biz.Export.ExportUtils; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicInitiativeChargeCodeExportService(StrategicOpportunityLayout layout, StrategicOpportunity strategicOpportunity) + { + private readonly List _allKeys = new List { "totalEncounters", "totalUnits", "unitsPerEncounter", "costPerEncounter", "costPerUnit", "totalCommittedCost", "totalActualCost", "totalVariance", "progress" }; + private readonly ImmutableList _layoutCurrencyColumns = ImmutableList.Create("costPerEncounter", "costPerUnit", "totalCommittedCost", "totalActualCost", "totalVariance"); + private readonly ImmutableList _layoutNumberColumns = ImmutableList.Create("totalEncounters", "totalUnits", "unitsPerEncounter"); + private readonly ImmutableDictionary> _layoutToColumns = new Dictionary>() + { + { "totalEncounters", new List() { "MonthTotalDistinctEncounters", "PeriodTotalDistinctEncounters" } }, + { "totalUnits", new List() { "MonthTotalUnits", "PeriodTotalUnits" } }, + { "unitsPerEncounter", new List() { "MonthUnitsPerEncounter", "PeriodUnitsPerEncounter" } }, + { "costPerEncounter", new List() { "MonthVariableDirectCostPerEncounter", "PeriodVariableDirectCostPerEncounter" } }, + { "costPerUnit", new List() { "MonthVariableDirectCostPerUnit", "PeriodVariableDirectCostPerUnit" } }, + { "totalCommittedCost", new List() { "MonthTotalCommittedCost", "PeriodTotalCommittedCost" } }, + { "totalActualCost", new List() { "MonthTotalActualCost", "PeriodTotalActualCost" } }, + { "totalVariance", new List() { "MonthTotalVariance", "PeriodTotalVariance" } }, + { "progress", new List() { "MonthProgress", "PeriodProgress" } }, + }.ToImmutableDictionary(); + + private readonly IImmutableDictionary _layoutToColumnNames = new Dictionary() + { + {"totalEncounters", "Total Distinct Encounters"}, + {"totalUnits", "Total Units"}, + {"unitsPerEncounter", "Units per Encounter"}, + {"costPerEncounter", "Variable Direct Cost per Encounter"}, + {"costPerUnit", "Variable Direct Cost per Unit"}, + {"totalCommittedCost", "Total Committed Variable Direct Cost"}, + {"totalActualCost", "Total Actual Variable Direct Cost"}, + {"totalVariance", "Total Variance"}, + {"progress", "Progress"} + }.ToImmutableDictionary(); + + private readonly IImmutableDictionary _layoutToColumnType = new Dictionary() + { + {"totalEncounters", typeof(double)}, + {"totalUnits", typeof(double)}, + {"unitsPerEncounter", typeof(double)}, + {"costPerEncounter", typeof(double)}, + {"costPerUnit", typeof(double)}, + {"totalCommittedCost", typeof(double)}, + {"totalActualCost", typeof(double)}, + {"totalVariance", typeof(double)}, + {"progress", typeof(string)} + }.ToImmutableDictionary(); + + + public System.Data.DataTable MapStrategicInitiativeToDataTable(StrategicOpportunityDetailData periodList, StrategicOpportunityDetailData monthList) + { + System.Data.DataTable table = new System.Data.DataTable(); + var rows = layout.Rows.Where(item => item != StrategicOpportunityDetailDimension.System.Value); + var columns = layout.Columns; + var monthDataByKey = monthList.Data.ToDictionary(m => m.Key); + + foreach (var colName in rows.Select(r => StrategicChargeCodeExportHelper.LayoutToRows[r])) + { + table.Columns.Add(colName); + }; + + foreach (var col in _allKeys) + { + if (columns.Contains(col)) + { + IEnumerable colInfo; + _layoutToColumns.TryGetValue(col, out colInfo); + + if (colInfo != null) + { + var colName = colInfo.FirstOrDefault(); + table.Columns.Add(new System.Data.DataColumn(colName, _layoutToColumnType[col]) { Caption = _layoutToColumnNames[col] }); + } + } + }; + + foreach (var col in _allKeys) + { + if (columns.Contains(col)) + { + IEnumerable colInfo; + _layoutToColumns.TryGetValue(col, out colInfo); + + if (colInfo != null) + { + var colName = _layoutToColumns[col].LastOrDefault(); + table.Columns.Add(new System.Data.DataColumn(colName, _layoutToColumnType[col]) { Caption = $"{_layoutToColumnNames[col]} " }); + } + } + }; + + var sortedData = new List(); + periodList.Data.ForEach(d => sortedData.Add(new StrategicOpportunityDetailGroupedWithParsedKey(d, periodList.DimensionsData, rows))); + + var data = sortedData.OrderBy(d => d.ParsedKey).Select(d => d.StrategicOpportunityDetailGrouped); + data.ForEach(periodItem => + { + DataRow row = table.NewRow(); + monthDataByKey.TryGetValue(periodItem.Key, out var monthItem); + + StrategicChargeCodeExportHelper.SetRowData(row, periodItem.Key, layout.Rows.Where(r => r != StrategicOpportunityDetailDimension.System.Value), periodList.DimensionsData); + + foreach (var c in layout.Columns) + { + float periodEncounters = 0; + float monthEncounters = 0; + monthList.EncounterData.TryGetValue(periodItem.Key, out monthEncounters); + periodList.EncounterData.TryGetValue(periodItem.Key, out periodEncounters); + + switch (c) + { + case "totalEncounters": + row["MonthTotalDistinctEncounters"] = monthEncounters; + row["PeriodTotalDistinctEncounters"] = periodEncounters; + break; + + case "totalUnits": + row["MonthTotalUnits"] = monthItem != null ? monthItem.Units : 0; + row["PeriodTotalUnits"] = periodItem.Units; + break; + case "unitsPerEncounter": + row["MonthUnitsPerEncounter"] = monthItem != null && monthEncounters != 0 ? monthItem.Units / monthEncounters : 0; + row["PeriodUnitsPerEncounter"] = periodEncounters != 0 ? periodItem.Units / periodEncounters : 0; + break; + case "costPerEncounter": + row["MonthVariableDirectCostPerEncounter"] = monthItem != null && monthEncounters != 0 ? monthItem.Cost / monthEncounters : 0; + row["PeriodVariableDirectCostPerEncounter"] = periodEncounters != 0 ? periodItem.Cost / periodEncounters : 0; + break; + case "costPerUnit": + row["MonthVariableDirectCostPerUnit"] = monthItem != null && monthItem.Units != 0 ? monthItem.Cost / monthItem.Units : 0; + row["PeriodVariableDirectCostPerUnit"] = periodItem.Units != 0 ? periodItem.Cost / periodItem.Units : 0; + break; + case "totalCommittedCost": + row["MonthTotalCommittedCost"] = monthItem != null ? monthItem.GoalCost : 0; + row["PeriodTotalCommittedCost"] = periodItem.GoalCost; + break; + + case "totalActualCost": + row["MonthTotalActualCost"] = monthItem != null ? monthItem.Cost : 0; + row["PeriodTotalActualCost"] = periodItem.Cost; + break; + + case "totalVariance": + row["MonthTotalVariance"] = monthItem != null ? monthItem.Cost - monthItem.GoalCost : 0; + row["PeriodTotalVariance"] = periodItem.Cost - periodItem.GoalCost; + break; + + case "progress": + row["MonthProgress"] = strategicOpportunity.Status != (byte)StrategicOpportunityStatus.NewInitiative ? (monthItem != null ? monthItem.Cost - monthItem.GoalCost <= 0 : true) ? "On Track" : "Off Track" : string.Empty; + row["PeriodProgress"] = strategicOpportunity.Status != (byte)StrategicOpportunityStatus.NewInitiative ? periodItem.Cost - periodItem.GoalCost <= 0 ? "On Track" : "Off Track" : string.Empty; + break; + } + } + + table.Rows.Add(row); + }); + + return table; + } + + public Export.ExportOptions GetExportOptions() + { + var exportOptions = new Export.ExportOptions { EmptyMessage = "No Initiatives" }; + + _layoutToColumnNames.ForEach((name) => + { + if (layout.Columns.Contains(name.Key)) + { + if (_layoutCurrencyColumns.Contains(name.Key)) + { + exportOptions.AddColumnOptions(name.Value, ExcelCurrencyFormatWithDecimals); + exportOptions.AddColumnOptions($"{name.Value} ", ExcelCurrencyFormatWithDecimals); + + } + else if (_layoutNumberColumns.Contains(name.Key)) + { + exportOptions.AddColumnOptions(name.Value, "Accounting2Dec"); + exportOptions.AddColumnOptions($"{name.Value} ", "Accounting2Dec"); + } + } + }); + + return exportOptions; + } + + public void StyleWorkSheet(XLWorkbook wb, DateOnly startDate, DateOnly endDate) + { + var ws = wb.Worksheets.First(); + ws.Name = "Charge Code Initiative"; + + var table = ws.Tables.First(); + var rowCount = layout.Rows.Where(r => r != StrategicOpportunityDetailDimension.System.Value).Count(); + var columnCount = layout.Columns.Count(); + + if (columnCount > 0) + { + var progressCol = ws.Range(1, rowCount + columnCount + 1, 1, rowCount + columnCount + columnCount).Merge().SetValue($"Progress to Date ({DateExtensions.GetMonthDifference(startDate, endDate, true)} months)"); + var monthCol = ws.Range(1, rowCount + 1, 1, rowCount + columnCount).Merge().SetValue(endDate.ToString("MMM, yyyy")); + StrategicChargeCodeExportHelper.StyleWorkSheet(table, [progressCol, monthCol]); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicItemDimensionSyncService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicItemDimensionSyncService.cs new file mode 100644 index 0000000..74ff8f3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicItemDimensionSyncService.cs @@ -0,0 +1,189 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.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.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicItemDimensionSyncService( + IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + ISnowflakeDatabaseContext databaseContext) : IStrategicItemDimensionSyncService + { + public async Task UpsertAsync(StrategicOpportunity opportunity, CancellationToken cancellationToken) + { + if (opportunity == null) + throw new ArgumentNullException(nameof(opportunity)); + + var userGuids = new List(); + if (opportunity.CostLeaderGuid != Guid.Empty) + userGuids.Add(opportunity.CostLeaderGuid); + if (opportunity.ExecutiveSponsorGuid != Guid.Empty) + userGuids.Add(opportunity.ExecutiveSponsorGuid); + if (opportunity.OwnerGuid != Guid.Empty) + userGuids.Add(opportunity.OwnerGuid); + + var stakeholderLinks = await centralDbContext.StakeholdersLinks + .Where(s => s.StrategicOpportunityId == opportunity.OpportunityId) + .ToListAsync(cancellationToken); + + if (stakeholderLinks.Any()) + userGuids.AddRange(stakeholderLinks.Select(s => s.UserGuid).Where(g => g != Guid.Empty)); + + var userNames = new Dictionary(); + if (userGuids.Any()) + { + var distinctGuids = userGuids.Distinct().ToList(); + await using var jazzDbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + var profiles = await jazzDbContext.UserProfiles + .Where(up => distinctGuids.Contains(up.UserGuid)) + .ToListAsync(cancellationToken); + userNames = profiles.ToDictionary(up => up.UserGuid, up => up.GetDisplayName()); + } + + var costLeader = opportunity.CostLeaderGuid != Guid.Empty && userNames.ContainsKey(opportunity.CostLeaderGuid) + ? userNames[opportunity.CostLeaderGuid] : string.Empty; + var executiveSponsor = opportunity.ExecutiveSponsorGuid != Guid.Empty && userNames.ContainsKey(opportunity.ExecutiveSponsorGuid) + ? userNames[opportunity.ExecutiveSponsorGuid] : string.Empty; + var owner = opportunity.OwnerGuid != Guid.Empty && userNames.ContainsKey(opportunity.OwnerGuid) + ? userNames[opportunity.OwnerGuid] : string.Empty; + + var stakeholderNames = stakeholderLinks + .Select(s => userNames.ContainsKey(s.UserGuid) ? userNames[s.UserGuid] : string.Empty) + .Where(name => !string.IsNullOrEmpty(name)) + .OrderBy(name => name, StringComparer.OrdinalIgnoreCase) + .ToList(); + var stakeholders = stakeholderNames.Any() ? string.Join(", ", stakeholderNames) : string.Empty; + + var parameters = new + { + StrategicItemId = opportunity.OpportunityId, + Name = opportunity.Name, + RollupOneId = opportunity.Rollup1Id, + RollupTwoId = opportunity.Rollup2Id, + RollupThreeId = opportunity.Rollup3Id, + BaselineType = opportunity.BaselineType.ToString().SplitCamelCase(), + StrategicItemType = opportunity.IsOpportunity ? "Opportunity" : "Initiative", + OpportunityType = opportunity.OpportunityType switch + { + StrategicOpportunityTypes.ChargeCode => "Charge Code", + StrategicOpportunityTypes.GeneralLedger => "General Ledger", + _ => opportunity.OpportunityType.ToString() + }, + Status = ((StrategicOpportunityStatus)opportunity.Status).ToString().SplitCamelCase(), + BaselineDistribution = opportunity.BaselineDistribution.ToString(), + BaselineStartDate = opportunity.BaselineStartDate != default(DateOnly) + ? opportunity.BaselineStartDate.ToString("yyyy-MM-dd") : (string)null, + BaselineEndDate = opportunity.BaselineEndDate != default(DateOnly) + ? opportunity.BaselineEndDate.ToString("yyyy-MM-dd") : (string)null, + EstimatedTrackingDuration = opportunity.EstimatedTrackingDuration, + TrackingStartDate = opportunity.TrackingStartDate != default(DateOnly) + ? opportunity.TrackingStartDate.ToString("yyyy-MM-dd") : (string)null, + TrackingEndDate = opportunity.TrackingEndDate != default(DateOnly) + ? opportunity.TrackingEndDate.ToString("yyyy-MM-dd") : (string)null, + EstimatedGoal = opportunity.EstimatedGoal, + TotalImprovement = opportunity.TotalImprovement, + CostLeader = costLeader, + ExecutiveSponsor = executiveSponsor, + Owner = owner, + Stakeholders = stakeholders, + TeamMembers = opportunity.TeamMembers ?? string.Empty, + LastRunTrackingDate = opportunity.LastRunTrackingDate?.ToString("yyyy-MM-dd"), + LastRunTrackingStatus = opportunity.LastRunTrackingStatus.ToString() + }; + + await databaseContext.ExecuteCommandAsync(BuildMergeSql(), parameters); + } + + public async Task DeleteAsync(long opportunityId) + { + var deleteSql = @"DELETE FROM cci.dimstrategicitem WHERE strategic_item_id = :StrategicItemId;"; + await databaseContext.ExecuteCommandAsync(deleteSql, new { StrategicItemId = opportunityId }); + } + + private static string BuildMergeSql() + { + return @" +MERGE INTO cci.dimstrategicitem AS target +USING ( + SELECT + :StrategicItemId AS strategic_item_id, + :Name AS name, + :RollupOneId AS rollup_one_id, + :RollupTwoId AS rollup_two_id, + :RollupThreeId AS rollup_three_id, + :BaselineType AS baseline_type, + :StrategicItemType AS strategic_item_type, + :OpportunityType AS opportunity_type, + :Status AS status, + :BaselineDistribution AS baseline_distribution, + :BaselineStartDate::DATE AS baseline_start_date, + :BaselineEndDate::DATE AS baseline_end_date, + :EstimatedTrackingDuration AS estimated_tracking_duration, + :TrackingStartDate::DATE AS tracking_start_date, + :TrackingEndDate::DATE AS tracking_end_date, + :EstimatedGoal AS estimated_goal, + :TotalImprovement AS total_improvement, + :CostLeader AS cost_leader, + :ExecutiveSponsor AS executive_sponsor, + :Owner AS owner, + :Stakeholders AS stakeholders, + :TeamMembers AS team_members, + :LastRunTrackingDate::DATE AS last_run_tracking_date, + :LastRunTrackingStatus AS last_run_tracking_status +) AS source +ON target.strategic_item_id = source.strategic_item_id +WHEN MATCHED THEN + UPDATE SET + name = source.name, + rollup_one_id = source.rollup_one_id, + rollup_two_id = source.rollup_two_id, + rollup_three_id = source.rollup_three_id, + baseline_type = source.baseline_type, + strategic_item_type = source.strategic_item_type, + opportunity_type = source.opportunity_type, + status = source.status, + baseline_distribution = source.baseline_distribution, + baseline_start_date = source.baseline_start_date, + baseline_end_date = source.baseline_end_date, + estimated_tracking_duration = source.estimated_tracking_duration, + tracking_start_date = source.tracking_start_date, + tracking_end_date = source.tracking_end_date, + estimated_goal = source.estimated_goal, + total_improvement = source.total_improvement, + cost_leader = source.cost_leader, + executive_sponsor = source.executive_sponsor, + owner = source.owner, + stakeholders = source.stakeholders, + team_members = source.team_members, + last_run_tracking_date = source.last_run_tracking_date, + last_run_tracking_status = source.last_run_tracking_status +WHEN NOT MATCHED THEN + INSERT ( + strategic_item_id, name, rollup_one_id, rollup_two_id, rollup_three_id, + baseline_type, strategic_item_type, opportunity_type, status, baseline_distribution, + baseline_start_date, baseline_end_date, estimated_tracking_duration, + tracking_start_date, tracking_end_date, estimated_goal, total_improvement, + cost_leader, executive_sponsor, owner, stakeholders, team_members, + last_run_tracking_date, last_run_tracking_status + ) + VALUES ( + source.strategic_item_id, source.name, source.rollup_one_id, source.rollup_two_id, source.rollup_three_id, + source.baseline_type, source.strategic_item_type, source.opportunity_type, source.status, source.baseline_distribution, + source.baseline_start_date, source.baseline_end_date, source.estimated_tracking_duration, + source.tracking_start_date, source.tracking_end_date, source.estimated_goal, source.total_improvement, + source.cost_leader, source.executive_sponsor, source.owner, source.stakeholders, source.team_members, + source.last_run_tracking_date, source.last_run_tracking_status);"; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunity.cs new file mode 100644 index 0000000..7089b70 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunity.cs @@ -0,0 +1,61 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Table("strategic_opportunity")] + public class StrategicOpportunity + { + [Key] + public long OpportunityId { get; set; } + public string Name { get; set; } + public StrategicOpportunityTypes OpportunityType { get; set; } + public string OpportunityFiltersJSON { get; set; } + public string LayoutJSON { get; set; } + public int Rollup1Id { get; set; } + public int Rollup2Id { get; set; } + public int Rollup3Id { get; set; } + public BaselineType BaselineType { get; set; } + public DistributionMethod BaselineDistribution { get; set; } + public DateOnly BaselineStartDate { get; set; } + public DateOnly BaselineEndDate { get; set; } + public DateOnly TrackingStartDate { get; set; } + public DateOnly TrackingEndDate { get; set; } + public byte EstimatedTrackingDuration { get; set; } + public double TotalImprovement { get; set; } + public double EstimatedGoal { get; set; } + public byte Status { get; set; } + public DateTime? LastRunTrackingDate { get; set; } + public RunTrackingStatus LastRunTrackingStatus { get; set; } + public int StrataId { get; set; } + public Guid OwnerGuid { get; set; } + public Guid ExecutiveSponsorGuid { get; set; } + public Guid CostLeaderGuid { get; set; } + [MaxLength(4000)] + public string TeamMembers { get; set; } = ""; + + [NotMapped] + public DateOnly GoalStartDate => BaselineStartDate.AddMonths(1); + + [NotMapped] + public DateOnly GoalEndDate => BaselineStartDate.AddMonths(EstimatedTrackingDuration); + + [NotMapped] + public bool IsOpportunity => Status < (byte)StrategicOpportunityStatus.NewInitiative; + + public ICollection Stakeholders { get; set; } + public ICollection Measures { get; set; } + public ICollection Workbooks { get; set; } + public ICollection RampUps { get; set; } + + public DateOnly GetTrackingEndDateForFilters() + { + // Return the first day of the month following TrackingEndDate + return new DateOnly(TrackingEndDate.Year, TrackingEndDate.Month, 1).AddMonths(1); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityChargeCode.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityChargeCode.cs new file mode 100644 index 0000000..2092723 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityChargeCode.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Table("strategic_opportunity_charge_code")] + public class StrategicOpportunityChargeCode: StrategicOpportunity + { + public string PatientPopulationHPath { get; set; } + public long ExplorationPopulationId { get; set; } + public string ChargeCodeHPath { get; set; } + public double TotalCommittedCost { get; set; } + public double ProgressToDateCommitted { get; set; } + public double ProgressToDateActual { get; set; } + + public StrategicOpportunityChargeCode() { } + + public StrategicOpportunityChargeCode(StrategicOpportunityChargeCode opportunity, string opportunityName) + { + Name = opportunityName; + OpportunityType = opportunity.OpportunityType; + OpportunityFiltersJSON = opportunity.OpportunityFiltersJSON; + Rollup1Id = opportunity.Rollup1Id; + Rollup2Id = opportunity.Rollup2Id; + Rollup3Id = opportunity.Rollup3Id; + BaselineType = opportunity.BaselineType; + BaselineDistribution = opportunity.BaselineDistribution; + BaselineStartDate = opportunity.BaselineStartDate; + BaselineEndDate = opportunity.BaselineEndDate; + EstimatedTrackingDuration = opportunity.EstimatedTrackingDuration; + StrataId = opportunity.StrataId; + LayoutJSON = opportunity.LayoutJSON; + + PatientPopulationHPath = opportunity.PatientPopulationHPath; + ExplorationPopulationId = opportunity.ExplorationPopulationId; + ChargeCodeHPath = opportunity.ChargeCodeHPath; + + TeamMembers = opportunity.TeamMembers; + OwnerGuid = opportunity.OwnerGuid; + ExecutiveSponsorGuid = opportunity.ExecutiveSponsorGuid; + CostLeaderGuid = opportunity.CostLeaderGuid; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityChargeCodeExportService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityChargeCodeExportService.cs new file mode 100644 index 0000000..97ea354 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityChargeCodeExportService.cs @@ -0,0 +1,179 @@ +using ClosedXML.Excel; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Data; +using System.Linq; +using static Strata.ContinuousImprovement.Biz.Export.ExportUtils; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicOpportunityChargeCodeExportService + { + private readonly ImmutableList _baselineKeys = ImmutableList.Create("encountersMonth", "unitsMonth", "unitsPerEncounter", "costPerEncounter", "costPerUnit", "costMonth"); + private readonly ImmutableList _goalKeys = ImmutableList.Create("goalUnitsMonth", "goalUnitsPerEncounter", "goalCostPerEncounter", "goalCostPerUnit", "goalCostMonth"); + private readonly StrategicOpportunityChargeCode _strategicOpportunity; + private readonly StrategicOpportunityLayout _layout; + private readonly ImmutableDictionary _layoutToColumns; + private readonly List _allKeys; + + public StrategicOpportunityChargeCodeExportService(StrategicOpportunityLayout layout, StrategicOpportunityChargeCode strategicOpportunity) : base() + { + var columns = new Dictionary() { + { "encountersMonth", new ExportHelper("Average Encounters", "Accounting" )}, + { "unitsMonth", new ExportHelper("Average Units", "Accounting" ) }, + { "unitsPerEncounter", new ExportHelper("Units per Encounter", "Accounting2Dec" ) }, + { "costPerEncounter", new ExportHelper("Variable Direct Cost per Encounter", ExcelCurrencyFormatWithDecimals )}, + { "costPerUnit", new ExportHelper("Variable Direct Cost per Unit", ExcelCurrencyFormatWithDecimals )}, + { "costMonth", new ExportHelper("Variable Direct Cost Total", ExcelCurrencyFormatWithoutDecimals )}, + { "goalUnitsMonth", new ExportHelper("Average Units ", "Accounting" )}, + { "goalUnitsPerEncounter", new ExportHelper("Units per Encounter ", "Accounting2Dec" )}, + { "goalCostPerEncounter", new ExportHelper("Variable Direct Cost per Encounter ", ExcelCurrencyFormatWithDecimals )}, + { "goalCostPerUnit", new ExportHelper("Variable Direct Cost per Unit ", ExcelCurrencyFormatWithDecimals )}, + { "goalCostMonth", new ExportHelper("Variable Direct Cost Total ", ExcelCurrencyFormatWithoutDecimals )}, + { "totalImprovement", new ExportHelper($"Total Improvement for {strategicOpportunity.EstimatedTrackingDuration} months", ExcelCurrencyFormatWithoutDecimals )}, + }; + + _strategicOpportunity = strategicOpportunity; + _layout = layout; + _layoutToColumns = columns.ToImmutableDictionary(); + _allKeys = columns.Keys.ToList(); + } + + public System.Data.DataTable MapStrategicOpportunityToDataTable(StrategicOpportunityDetailData opportunitiesData) + { + System.Data.DataTable table = new System.Data.DataTable(); + var monthCount = DateExtensions.GetMonthDifference(_strategicOpportunity.BaselineStartDate, _strategicOpportunity.BaselineEndDate, true); + var estimatedMonthCount = _strategicOpportunity.EstimatedTrackingDuration; + var monthCountMultiplier = monthCount != 0 ? (double)estimatedMonthCount / monthCount : 0; + var rows = _layout.Rows.Where(item => item != StrategicOpportunityDetailDimension.System.Value); + var columns = _layout.Columns; + + foreach (var colName in rows.Select(r => StrategicChargeCodeExportHelper.LayoutToRows[r])) + { + table.Columns.Add(colName); + }; + + foreach (var col in _allKeys) + { + if (columns.Contains(col)) + { + ExportHelper colInfo; + _layoutToColumns.TryGetValue(col, out colInfo); + + if (colInfo != null) + table.Columns.Add(new System.Data.DataColumn(col, typeof(double)) { Caption = colInfo.ColumnCaption }); + } + }; + + var sortedData = new List(); + opportunitiesData.Data.ForEach(o => sortedData.Add(new StrategicOpportunityDetailGroupedWithParsedKey(o, opportunitiesData.DimensionsData, rows))); + + var data = sortedData.OrderBy(d => d.ParsedKey).Select(d => d.StrategicOpportunityDetailGrouped).GroupBy(o => o.Key).ToDictionary(o => o.Key, o => o.ToList()); + data.ForEach(item => + { + DataRow row = table.NewRow(); + StrategicChargeCodeExportHelper.SetRowData(row, item.Key, _layout.Rows.Where(r => r != StrategicOpportunityDetailDimension.System.Value), opportunitiesData.DimensionsData); + + foreach (var c in _layout.Columns) + { + float encounters = 0; + var baselineValues = item.Value.Where(i => i.IsBaseline).ToList(); + var notBaselineValues = item.Value.Where(i => !i.IsBaseline).ToList(); + var baselineUnitsSum = baselineValues.Sum(i => i.Units); + var baselineCostSum = baselineValues.Sum(i => i.Cost); + var goalUnitsSum = notBaselineValues.Sum(i => i.GoalUnits); + var goalCostSum = notBaselineValues.Sum(i => i.GoalCost); + opportunitiesData.EncounterData.TryGetValue(item.Key, out encounters); + + switch (c) + { + case "encountersMonth": + row[c] = monthCount != 0 ? encounters / monthCount : 0; + break; + case "unitsMonth": + row[c] = monthCount != 0 ? baselineUnitsSum / monthCount : 0; + break; + case "unitsPerEncounter": + row[c] = Math.Abs(encounters) > float.Epsilon ? baselineUnitsSum / encounters : 0; + break; + case "costPerEncounter": + row[c] = Math.Abs(encounters) > float.Epsilon ? baselineCostSum / encounters : 0; + break; + case "costPerUnit": + row[c] = Math.Abs(baselineUnitsSum) > float.Epsilon ? baselineCostSum / baselineUnitsSum : 0; break; + case "costMonth": + row[c] = monthCount != 0 ? baselineCostSum / monthCount : 0; + break; + case "goalUnitsMonth": + row[c] = estimatedMonthCount != 0 ? goalUnitsSum / estimatedMonthCount : 0; + break; + case "goalUnitsPerEncounter": + row[c] = Math.Abs(encounters) > float.Epsilon && Math.Abs(monthCountMultiplier) > float.Epsilon ? goalUnitsSum / (encounters * monthCountMultiplier) : 0; + break; + case "goalCostPerEncounter": + row[c] = Math.Abs(encounters) > float.Epsilon && Math.Abs(monthCountMultiplier) > float.Epsilon ? goalCostSum / (encounters * monthCountMultiplier) : 0; + break; + case "goalCostPerUnit": + row[c] = Math.Abs(goalUnitsSum) > float.Epsilon ? goalCostSum / goalUnitsSum : 0; + break; + case "goalCostMonth": + row[c] = estimatedMonthCount != 0 ? goalCostSum / estimatedMonthCount : 0; + break; + case "totalImprovement": + row[c] = monthCount != 0 && estimatedMonthCount != 0 ? ((baselineCostSum / monthCount) - (goalCostSum / estimatedMonthCount)) * estimatedMonthCount : 0; + break; + } + } + + table.Rows.Add(row); + }); + + return table; + } + + public Export.ExportOptions GetExportOptions() + { + var exportOptions = new Export.ExportOptions { EmptyMessage = "No Opportunities" }; + + _layoutToColumns.ForEach((column) => + { + if (_layout.Columns.Contains(column.Key)) + { + exportOptions.AddColumnOptions(column.Value.ColumnCaption, column.Value.ColumnFormat); + } + }); + + return exportOptions; + } + + public void StyleWorkSheet(XLWorkbook wb) + { + var ws = wb.Worksheets.First(); + ws.Name = $"Charge Code Opportunity"; + + var table = ws.Tables.First(); + var goalColumnsCount = _layout.Columns.Where(col => _goalKeys.Contains(col)).Count(); + var baselineColumnsCount = _layout.Columns.Where(col => _baselineKeys.Contains(col)).Count(); + var rowCount = _layout.Rows.Where(r => r != StrategicOpportunityDetailDimension.System.Value).Count(); + var headerRanges = new List(); + + if (goalColumnsCount > 0) + { + var goalCols = ws.Range(1, rowCount + baselineColumnsCount + 1, 1, rowCount + baselineColumnsCount + goalColumnsCount).Merge().SetValue("Monthly Goal"); + headerRanges.Add(goalCols); + } + + if (baselineColumnsCount > 0) + { + var baselineCols = ws.Range(1, rowCount + 1, 1, rowCount + baselineColumnsCount).Merge().SetValue("Monthly Baseline"); + headerRanges.Add(baselineCols); + } + + StrategicChargeCodeExportHelper.StyleWorkSheet(table, headerRanges.ToArray()); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityDetail.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityDetail.cs new file mode 100644 index 0000000..15374e1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityDetail.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Table("FactStrategicChargeCodeDetail", Schema = "cci")] + public class StrategicOpportunityDetail + { + public long RowId { get; set; } + public bool IsBaseline { get; set; } + public short DischargeFiscalYearId { get; set; } + public byte DischargeFiscalMonthId { get; set; } + public long OpportunityId { get; set; } + public int CaseTypeFamilyId { get; set; } + public int DepartmentId { get; set; } + public int ChargeCodeId { get; set; } + public int EntityId { get; set; } + public int PrimaryPhysicianId { get; set; } + public int PrimaryServiceLineId { get; set; } + public int PhysicianSpecialtyId { get; set; } + public string CostDriver { get; set; } + public double Units { get; set; } + public double Cost { get; set; } + public double DistributedUnits { get; set; } + public double DistributedCost { get; set; } + public double GoalUnits { get; set; } + public double GoalCost { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityGL.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityGL.cs new file mode 100644 index 0000000..3221eb7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityGL.cs @@ -0,0 +1,50 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Table("strategic_opportunity_gl")] + public class StrategicOpportunityGL : StrategicOpportunity + { + public GLAccountType AccountType { get; set; } + public string? DepartmentHPath { get; set; } + public string? RevenueAccountHPath { get; set; } + public string? ExpenseAccountHPath { get; set; } + public string? StatisticAccountHPath { get; set; } + public bool HasStatistic { get; set; } + public double TotalCommittedCost { get; set; } + public double ProgressToDateCommitted { get; set; } + public double ProgressToDateActual { get; set; } + + public StrategicOpportunityGL() { } + + public StrategicOpportunityGL(StrategicOpportunityGL opportunity, string opportunityName) + { + Name = opportunityName; + OpportunityType = opportunity.OpportunityType; + OpportunityFiltersJSON = opportunity.OpportunityFiltersJSON; + Rollup1Id = opportunity.Rollup1Id; + Rollup2Id = opportunity.Rollup2Id; + Rollup3Id = opportunity.Rollup3Id; + BaselineType = opportunity.BaselineType; + BaselineDistribution = opportunity.BaselineDistribution; + BaselineStartDate = opportunity.BaselineStartDate; + BaselineEndDate = opportunity.BaselineEndDate; + EstimatedTrackingDuration = opportunity.EstimatedTrackingDuration; + StrataId = opportunity.StrataId; + LayoutJSON = opportunity.LayoutJSON; + + AccountType = opportunity.AccountType; + DepartmentHPath = opportunity.DepartmentHPath; + RevenueAccountHPath = opportunity.RevenueAccountHPath; + ExpenseAccountHPath = opportunity.ExpenseAccountHPath; + StatisticAccountHPath = opportunity.StatisticAccountHPath; + HasStatistic = opportunity.HasStatistic; + + TeamMembers = opportunity.TeamMembers; + OwnerGuid = opportunity.OwnerGuid; + ExecutiveSponsorGuid = opportunity.ExecutiveSponsorGuid; + CostLeaderGuid = opportunity.CostLeaderGuid; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityGLDetail.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityGLDetail.cs new file mode 100644 index 0000000..488c690 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityGLDetail.cs @@ -0,0 +1,25 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicOpportunityGLDetail + { + public long RowId { get; set; } + public bool IsBaseline { get; set; } + public short FiscalYearId { get; set; } + public byte FiscalMonthId { get; set; } + public long OpportunityId { get; set; } + public int DepartmentId { get; set; } + public int AccountId { get; set; } + public int EntityId { get; set; } + public string? GlRollup { get; set; } + public byte? AccountType { get; set; } + public double Amount { get; set; } + public double GoalAmount { get; set; } + public double DistributedAmount { get; set; } + public double RevenueAmount { get; set; } + public double ExpenseAmount { get; set; } + public double StatisticAmount { get; set; } + public double GoalRevenueAmount { get; set; } + public double GoalExpenseAmount { get; set; } + public double GoalStatisticAmount { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityJob.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityJob.cs new file mode 100644 index 0000000..f6c6f75 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityJob.cs @@ -0,0 +1,119 @@ +using Hangfire; +using Hangfire.Throttling; +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Configurations.Hangfire; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.Hangfire.Filters; +using Strata.Hangfire.Filters.Claims; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Throttling(ThrottlingAction.DeleteJob)] + public class StrategicOpportunityJob(CentralDbContext centralDbContext, + IStrategicChargeCodeOpportunityDetailService strategicChargeCodeOpportunityDetailService, + IStrategicGLOpportunityDetailService strategicGLOpportunityDetailService, + IBackgroundJobClient backgroundJobClient, + IStrategicOpportunityService strategicOpportunityService) + { + [DeleteOnSuccess] + [JobDisplayName("Run tracking task by user interaction for Strategic Opportunity w/ ID {1}")] + [CaptureClaimsPrincipal] + [Mutex("strataId:{0}:opportunityId:{1}")] + public async Task RunOpportunityTracking(int strataId, long opportunityId, CancellationToken cancellationToken) + { + var ccOpportunity = await centralDbContext.StrategicOpportunityChargeCodes.Include(opp => opp.Measures) + .FirstOrDefaultAsync(so => so.OpportunityId == opportunityId, cancellationToken: cancellationToken); + if (ccOpportunity != null) + { + await strategicChargeCodeOpportunityDetailService.PopulateTrackingDetails(ccOpportunity, cancellationToken); + return; + } + + var glOpportunity = await centralDbContext.StrategicOpportunityGLs.Include(opp => opp.Measures) + .FirstOrDefaultAsync(so => so.OpportunityId == opportunityId, cancellationToken: cancellationToken); + if (glOpportunity != null) + { + await strategicGLOpportunityDetailService.PopulateTrackingDetails(glOpportunity, cancellationToken); + } + } + + [DeleteOnSuccess] + [JobDisplayName("Run UpdateMeasuresAverage task by user interaction for Strategic Opportunity w/ ID {1}")] + [CaptureClaimsPrincipal] + [Mutex("strataId:{0}:opportunityId:{1}")] + public async Task RunOpportunityUpdateMeasuresAverage(int strataId, long opportunityId, CancellationToken cancellationToken) + { + var opportunity = await centralDbContext.StrategicOpportunityChargeCodes.Include(opp => opp.Measures) + .FirstOrDefaultAsync(so => so.OpportunityId == opportunityId, cancellationToken: cancellationToken); + + if (opportunity == null) return; + + await strategicChargeCodeOpportunityDetailService.UpdateMeasuresAverage(opportunity, cancellationToken); + } + + [DeleteOnSuccess] + [JobDisplayName("Reset ramp-ups, run populate details, and update baseline date for Strategic Opportunity w/ ID {1}")] + [CaptureClaimsPrincipal] + [Mutex("strataId:{0}:opportunityId:{1}")] + public async Task RefreshAssociatedOpportunity(int strataId, long opportunityId, CancellationToken cancellationToken) + { + var ccOpportunity = await centralDbContext.StrategicOpportunityChargeCodes + .FirstOrDefaultAsync(so => so.OpportunityId == opportunityId && so.StrataId == strataId, cancellationToken); + + if (ccOpportunity != null && ccOpportunity.IsOpportunity) + { + var population = await centralDbContext.ExplorationPopulations + .FirstOrDefaultAsync(p => p.ExplorationPopulationId == ccOpportunity.ExplorationPopulationId && p.StrataId == strataId, cancellationToken: cancellationToken); + + if (population != null) + { + var populationDischargeDate = population.GetDischargeDates(); + var baselineStartDate = DateOnly.FromDateTime(populationDischargeDate.Item1); + var baselineEndDate = DateOnly.FromDateTime(populationDischargeDate.Item2); + + if (ccOpportunity.BaselineStartDate != baselineStartDate || ccOpportunity.BaselineEndDate != baselineEndDate) + { + ccOpportunity.BaselineStartDate = baselineStartDate; + ccOpportunity.BaselineEndDate = baselineEndDate; + await centralDbContext.SaveChangesAsync(cancellationToken); + } + } + + await strategicOpportunityService.ResetStrategicOpportunityRampUps(opportunityId, cancellationToken); + await strategicChargeCodeOpportunityDetailService.PopulateStrategicOpportunityDetails(ccOpportunity, cancellationToken); + return; + } + + var glOpportunity = await centralDbContext.StrategicOpportunityGLs + .FirstOrDefaultAsync(so => so.OpportunityId == opportunityId && so.StrataId == strataId, cancellationToken); + + if (glOpportunity != null && glOpportunity.IsOpportunity) + { + await strategicOpportunityService.ResetStrategicOpportunityRampUps(opportunityId, cancellationToken); + await strategicGLOpportunityDetailService.PopulateStrategicOpportunityGLDetails(glOpportunity, cancellationToken); + } + } + + [DeleteOnSuccess] + [JobDisplayName("Run tracking task for all Strategic Opportunities")] + [ImpersonateStrataUser] + [Mutex("strataId:{1}")] + public async Task RunTracking([DatabaseGuid] Guid databaseGuid, [StrataId] int strataId, CancellationToken cancellationToken) + { + // update tracking details for tracking initiatives and measure details for both tracking and complete monitoring initiatives + var trackingInitiatives = await centralDbContext.StrategicOpportunities.Where(o => (o.Status == (byte)StrategicOpportunityStatus.Tracking + || o.Status == (byte)StrategicOpportunityStatus.CompleteMonitoring) && o.StrataId == strataId).ToListAsync(cancellationToken); + if (!trackingInitiatives.Any()) return; + + foreach (var initiative in trackingInitiatives) + { + backgroundJobClient.Enqueue((job) => job.RunOpportunityTracking(strataId, initiative.OpportunityId, CancellationToken.None)); + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityLayout.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityLayout.cs new file mode 100644 index 0000000..946e9c4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityLayout.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicOpportunityLayout + { + public ICollection Rows { get; set; } + public ICollection Columns { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityMapper.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityMapper.cs new file mode 100644 index 0000000..04b9369 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityMapper.cs @@ -0,0 +1,26 @@ +using Riok.Mapperly.Abstractions; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Mapper()] + public static partial class StrategicOpportunityMapper + { + [MapperIgnoreSource(nameof(StrategicOpportunityChargeCodeDto.Measures))] + [MapperIgnoreSource(nameof(StrategicOpportunityChargeCodeDto.WorkbookIds))] + public static partial StrategicOpportunityChargeCode MapToStrategicOpportunityChargeCode(this StrategicOpportunityChargeCodeDto src); + + [MapperIgnoreSource(nameof(StrategicOpportunityGLDto.Measures))] + [MapperIgnoreSource(nameof(StrategicOpportunityGLDto.WorkbookIds))] + public static partial StrategicOpportunityGL MapToStrategicOpportunityGL(this StrategicOpportunityGLDto src); + + [MapperIgnoreSource(nameof(StrategicOpportunityMeasureDto.MeasureId))] + public static partial StrategicOpportunityMeasure MapToStrategicOpportunityMeasure(this StrategicOpportunityMeasureDto src); + + public static partial StrategicOpportunityMeasureDto MapToStrategicOpportunityMeasureDto(this StrategicOpportunityMeasure src); + + [MapperIgnoreSource(nameof(StrategicOpportunityWorkbook.StrataId))] + [MapperIgnoreSource(nameof(StrategicOpportunityWorkbook.Opportunity))] + public static partial StrategicOpportunityWorkbookDto MapToStrategicOpportunityWorkbookDto(this StrategicOpportunityWorkbook src); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityMeasure.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityMeasure.cs new file mode 100644 index 0000000..511f555 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityMeasure.cs @@ -0,0 +1,25 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Table("strategic_opportunity_measure")] + public class StrategicOpportunityMeasure + { + [Key] + public long MeasureId { get; set; } + public long OpportunityId { get; set; } + public MeasureType MeasureType { get; set; } + public string MeasureName { get; set; } + public DesiredResult DesiredResult { get; set; } + public Single AcceptableThreshold { get; set; } + public string FiltersJSON { get; set; } + public MeasureGroup MeasureGroup { get; set; } + public double AverageValue { get; set; } + public int StrataId { get; set; } + + public StrategicOpportunity Opportunity { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityMeasureDetail.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityMeasureDetail.cs new file mode 100644 index 0000000..df50d7a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityMeasureDetail.cs @@ -0,0 +1,10 @@ +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicOpportunityMeasureDetail + { + public long MeasureId { get; set; } + public short YearId { get; set; } + public byte MonthId { get; set; } + public double Value { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityRampUp.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityRampUp.cs new file mode 100644 index 0000000..1020149 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityRampUp.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Table("strategic_opportunity_ramp_up")] + public class StrategicOpportunityRampUp + { + public long OpportunityId { get; set; } + public byte MonthNumber { get; set; } + public double Percentage { get; set; } + + public StrategicOpportunity Opportunity { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityService.cs new file mode 100644 index 0000000..5ab8fd6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityService.cs @@ -0,0 +1,1354 @@ +using Dapper; +using Hangfire; +using IdentityModel; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using MoreLinq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.InitiativeRollupColumns; +using Strata.ContinuousImprovement.Biz.OpportunityRoleAssignment; +using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.DistributionProcess.Models; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Enums; +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Queries; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.Id.Client; +using Strata.Schema.Client; +using Strata.Schema.Client.Dtos; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using Strata.SqlTools.EntityFramework; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + public class StrategicOpportunityService( + IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + ISchemaServiceClient schemaServiceClient, + ISnowflakeDatabaseContext databaseContext, + IIdServiceClient idServiceClient, + IOpportunityRoleAssignmentService opportunityRoleAssignmentService, + IInitiativeRollupColumnService rollupColumnService, + IOpportunityWorkbookService opportunityWorkbookService, + IBackgroundJobClient backgroundJobClient, + IStrategicChargeCodeOpportunityDetailService strategicChargeCodeOpportunityDetailService, + IStrategicItemDimensionSyncService strategicItemDimensionSyncService, + IStrategicGLOpportunityDetailService strategicGLOpportunityDetailService, + ILogger logger = default) : JazzDbService(dbContextFactory, centralDbContext, claimsPrincipalAccessor), IStrategicOpportunityService + { + public const string StrategicOpportunityDetailTableName = "cci.FactStrategicChargeCodeDetail"; + public const string StrategicOpportunityGLDetailTableName = "cci.FactStrategicGLDetail"; + + public static readonly Dictionary DimensionsConfig = new Dictionary() + { + { StrategicOpportunityDetailDimension.Entity, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectEntityKeyValue.sql", ColumnName = "EntityID", AliasColumnName = "dsspesEntityID" } }, + { StrategicOpportunityDetailDimension.PrimaryServiceLine, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectServiceLineKeyValue.sql", ColumnName = "PrimaryServiceLineID", AliasColumnName = "dsspesPrimaryServiceLineId" } }, + { StrategicOpportunityDetailDimension.CaseTypeFamily, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectCaseTypeFamilyKeyValue.sql", ColumnName = "CaseTypeFamilyId", AliasColumnName = "CaseTypeFamilyId" } }, + { StrategicOpportunityDetailDimension.Physician, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectPhysicianKeyValue.sql", ColumnName = "PrimaryPhysicianID", AliasColumnName = "dsspesPhysicianId" } }, + { StrategicOpportunityDetailDimension.PhysicianSpecialty, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectPhysicianSpecialtyKeyValue.sql", ColumnName = "PhysicianSpecialtyID", AliasColumnName = "physPrimaryPhysicianSpecialtyId"} }, + { StrategicOpportunityDetailDimension.CostDriver, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectCostDriverKeyValue.sql", ColumnName = "CostDriver" , AliasColumnName = "ccCostDriver"} }, + { StrategicOpportunityDetailDimension.Department, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectDepartmentKeyValue.sql", ColumnName = "DepartmentID", AliasColumnName = "pblidDepartmentId"} }, + { StrategicOpportunityDetailDimension.ChargeCode, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectChargeCodeKeyValue.sql", ColumnName = "ChargeCodeID", AliasColumnName = "ccChargeCodeID"} } + }; + + public static readonly Dictionary GLDimensionsConfig = new Dictionary() + { + { StrategicOpportunityDetailDimension.Entity, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectGLEntityKeyValue.sql", ColumnName = "EntityId" } }, + { StrategicOpportunityDetailDimension.Department, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectDepartmentKeyValue.sql", ColumnName = "DepartmentId" } }, + { StrategicOpportunityDetailDimension.GLRollup, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectGLRollupKeyValue.sql", ColumnName = "GLRollup", IsKeyString = true } }, + { StrategicOpportunityDetailDimension.GLAccount, new StrategicOpportunityDetailDimensionConfig(){ SqlFile = "SelectGLAccountKeyValue.sql", ColumnName = "AccountId" } } + }; + + public async Task> GetStrategicOpportunitiesAsync(CancellationToken cancellationToken) + { + try + { + var opportunities = await GetAllStrategicChargeCodes(item => item.IsOpportunity, cancellationToken); + var rollups = await rollupColumnService.GetAllAsync(cancellationToken); + + var results = opportunities.Select(so => new StrategicOpportunityItem + { + OpportunityId = so.OpportunityId, + Name = so.Name, + OpportunityType = so.OpportunityType, + OpportunityTypeName = GetOpportunityTypeName(so.OpportunityType), + Rollup1 = GetRollupName(rollups, so.Rollup1Id, 0), + Rollup2 = GetRollupName(rollups, so.Rollup2Id, 1), + Rollup3 = GetRollupName(rollups, so.Rollup3Id, 2), + PatientPopulationHPath = so.PatientPopulationHPath, + ExplorationPopulationId = so.ExplorationPopulationId, + EncounterGroupName = string.IsNullOrEmpty(so.PatientPopulationHPath) ? "Exploration Patient Population" : "Patient Population", + AdditionalFilters = !string.IsNullOrEmpty(so.OpportunityFiltersJSON) && so.OpportunityFiltersJSON != "[]" ? "Yes" : "No", + BaselinePeriod = $"{so.BaselineStartDate:MMM d, yyyy} - {so.BaselineEndDate:MMM d, yyyy}", + EstimatedTrackDurationMonths = so.EstimatedTrackingDuration, + BaselineStartDate = so.BaselineStartDate, + TotalImprovement = so.TotalImprovement, + EstimatedGoal = so.EstimatedGoal, + StatusName = GetStatusName(so.Status) + }).ToList(); + await SetPopulationName(results, cancellationToken); + + var glOpportunities = await GetAllStrategicGLOpportunities(item => item.IsOpportunity, cancellationToken); + var glResults = glOpportunities.Select(so => new StrategicOpportunityItem + { + OpportunityId = so.OpportunityId, + Name = so.Name, + OpportunityType = so.OpportunityType, + OpportunityTypeName = GetOpportunityTypeName(so.OpportunityType), + Rollup1 = GetRollupName(rollups, so.Rollup1Id, 0), + Rollup2 = GetRollupName(rollups, so.Rollup2Id, 1), + Rollup3 = GetRollupName(rollups, so.Rollup3Id, 2), + EncounterGroupName = "General Ledger", + AdditionalFilters = !string.IsNullOrEmpty(so.OpportunityFiltersJSON) && so.OpportunityFiltersJSON != "[]" ? "Yes" : "No", + BaselinePeriod = $"{so.BaselineStartDate:MMM d, yyyy} - {so.BaselineEndDate:MMM d, yyyy}", + EstimatedTrackDurationMonths = so.EstimatedTrackingDuration, + BaselineStartDate = so.BaselineStartDate, + TotalImprovement = so.TotalImprovement, + EstimatedGoal = so.EstimatedGoal, + StatusName = GetStatusName(so.Status) + }).ToList(); + + results.AddRange(glResults); + return results; + } + catch (Exception ex) + { + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + public async Task> GetStrategicInitiativesAsync(CancellationToken cancellationToken) + { + try + { + var initiatives = await GetAllStrategicChargeCodes(item => !item.IsOpportunity, cancellationToken); + var rollups = await rollupColumnService.GetAllAsync(cancellationToken); + var strategicOpportunities = await _centralDbContext.StrategicOpportunities.ToListAsync(cancellationToken); + + var results = initiatives + .Join(strategicOpportunities, i => i.OpportunityId, so => so.OpportunityId, (i, so) => new { Initiative = i, StrategicOpportunity = so }) + .Select(item => new StrategicInitiativeItem + { + OpportunityId = item.Initiative.OpportunityId, + Name = item.Initiative.Name, + OpportunityType = item.Initiative.OpportunityType, + OpportunityTypeName = GetOpportunityTypeName(item.Initiative.OpportunityType), + Rollup1 = GetRollupName(rollups, item.Initiative.Rollup1Id, 0), + Rollup2 = GetRollupName(rollups, item.Initiative.Rollup2Id, 1), + Rollup3 = GetRollupName(rollups, item.Initiative.Rollup3Id, 2), + PatientPopulationHPath = item.Initiative.PatientPopulationHPath, + ExplorationPopulationId = item.Initiative.ExplorationPopulationId, + EncounterGroupName = string.IsNullOrEmpty(item.Initiative.PatientPopulationHPath) ? "Exploration Patient Population" : "Patient Population", + AdditionalFilters = !string.IsNullOrEmpty(item.Initiative.OpportunityFiltersJSON) && item.Initiative.OpportunityFiltersJSON != "[]" ? "Yes" : "No", + BaselinePeriod = $"{item.Initiative.BaselineStartDate:MMM d, yyyy} - {item.Initiative.BaselineEndDate:MMM d, yyyy}", + TrackingPeriod = $"{item.Initiative.TrackingStartDate:MMM d, yyyy} - {item.Initiative.TrackingEndDate:MMM d, yyyy}", + TotalCommitted = item.Initiative.TotalCommittedCost, + ProgressToDateActual = item.Initiative.ProgressToDateActual, + ProgressToDateCommitted = item.Initiative.ProgressToDateCommitted, + ProgressToDateVariance = item.Initiative.ProgressToDateActual - item.Initiative.ProgressToDateCommitted, + StatusName = GetStatusName(item.Initiative.Status), + LastRunTrackingDate = item.StrategicOpportunity.LastRunTrackingDate, + LastRunTrackingStatus = (byte)item.StrategicOpportunity.LastRunTrackingStatus, + OnTrack = item.Initiative.ProgressToDateActual <= item.Initiative.ProgressToDateCommitted + }).ToList(); + await SetPopulationName(results, cancellationToken); + + var glInitiatives = await GetAllStrategicGLOpportunities(item => !item.IsOpportunity, cancellationToken); + var glResults = glInitiatives + .Join(strategicOpportunities, i => i.OpportunityId, so => so.OpportunityId, (i, so) => new { Initiative = i, StrategicOpportunity = so }) + .Select(item => new StrategicInitiativeItem + { + OpportunityId = item.Initiative.OpportunityId, + Name = item.Initiative.Name, + OpportunityType = item.Initiative.OpportunityType, + OpportunityTypeName = GetOpportunityTypeName(item.Initiative.OpportunityType), + Rollup1 = GetRollupName(rollups, item.Initiative.Rollup1Id, 0), + Rollup2 = GetRollupName(rollups, item.Initiative.Rollup2Id, 1), + Rollup3 = GetRollupName(rollups, item.Initiative.Rollup3Id, 2), + EncounterGroupName = "General Ledger", + AdditionalFilters = !string.IsNullOrEmpty(item.Initiative.OpportunityFiltersJSON) && item.Initiative.OpportunityFiltersJSON != "[]" ? "Yes" : "No", + BaselinePeriod = $"{item.Initiative.BaselineStartDate:MMM d, yyyy} - {item.Initiative.BaselineEndDate:MMM d, yyyy}", + TrackingPeriod = $"{item.Initiative.TrackingStartDate:MMM d, yyyy} - {item.Initiative.TrackingEndDate:MMM d, yyyy}", + TotalCommitted = item.Initiative.TotalCommittedCost, + ProgressToDateActual = item.Initiative.ProgressToDateActual, + ProgressToDateCommitted = item.Initiative.ProgressToDateCommitted, + ProgressToDateVariance = item.Initiative.ProgressToDateActual - item.Initiative.ProgressToDateCommitted, + OnTrack = item.Initiative.AccountType == (byte)GLAccountType.Revenue + ? item.Initiative.ProgressToDateActual >= item.Initiative.ProgressToDateCommitted + : item.Initiative.ProgressToDateActual <= item.Initiative.ProgressToDateCommitted, + StatusName = GetStatusName(item.Initiative.Status), + LastRunTrackingDate = item.StrategicOpportunity.LastRunTrackingDate, + LastRunTrackingStatus = (byte)item.StrategicOpportunity.LastRunTrackingStatus + }).ToList(); + + results.AddRange(glResults); + return results; + } + catch (Exception ex) + { + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + public async Task GetStrategicOpportunityChargeCode(long opportunityId, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunityChargeCodes.SingleOrDefaultAsync(opp => opp.OpportunityId == opportunityId, cancellationToken); + if (strategicOpportunity == null) return null; + + var data = new StrategicOpportunityChargeCodeEditData + { + Name = strategicOpportunity.Name, + Rollup1Id = strategicOpportunity.Rollup1Id, + Rollup2Id = strategicOpportunity.Rollup2Id, + Rollup3Id = strategicOpportunity.Rollup3Id, + EncounterGroupName = string.IsNullOrEmpty(strategicOpportunity.PatientPopulationHPath) ? "Exploration Patient Population" : "Patient Population", + PatientPopulationHPath = strategicOpportunity.PatientPopulationHPath, + ExplorationPopulationId = strategicOpportunity.ExplorationPopulationId, + ChargeCodeHPath = [.. strategicOpportunity.ChargeCodeHPath.Split(",")], + Filters = strategicOpportunity.OpportunityFiltersJSON, + BaselineStartDate = strategicOpportunity.BaselineStartDate, + BaselineEndDate = strategicOpportunity.BaselineEndDate, + BaselineDistribution = strategicOpportunity.BaselineDistribution, + EstimatedTrackingDuration = strategicOpportunity.EstimatedTrackingDuration + }; + + // populate strategic opportunity populations + if (strategicOpportunity.ExplorationPopulationId != 0) + { + var explorationPopulation = await _centralDbContext.ExplorationPopulations + .FirstOrDefaultAsync(ex => ex.ExplorationPopulationId == strategicOpportunity.ExplorationPopulationId, cancellationToken); + var populationMembers = new List + { + new() + { + Id = (int)strategicOpportunity.ExplorationPopulationId, + Name = explorationPopulation.Name + } + }; + data.PopulationMembers = populationMembers; + } + else + { + var patientPopulationsHPath = strategicOpportunity.PatientPopulationHPath.Split(",").ToList(); + var populationMembers = GetDimensionMembersByHierarchyPaths("Clinical Indicator", patientPopulationsHPath, cancellationToken).Result; + data.PopulationMembers = populationMembers; + } + + // populate strategic opportunity charge codes + var chargeCodeHPaths = strategicOpportunity.ChargeCodeHPath.Split(",").ToList(); + var strategicOpportunityChargeCodes = GetDimensionMembersByHierarchyPaths("Charge Code", chargeCodeHPaths, cancellationToken).Result; + data.ChargeCodes = strategicOpportunityChargeCodes; + + // populate strategic opportunity measures + var measures = await _centralDbContext.StrategicOpportunityMeasures.Where(x => x.OpportunityId == strategicOpportunity.OpportunityId).ToListAsync(cancellationToken); + data.Measures = measures.Select(m => m.MapToStrategicOpportunityMeasureDto()).ToList(); + + // populate strategic opportunity workbooks + var strategicOpportunityWorkbooks = await _centralDbContext.StrategicOpportunityWorkbooks.Where(x => x.OpportunityId == strategicOpportunity.OpportunityId).ToListAsync(cancellationToken); + var workbooks = await opportunityWorkbookService.GetAllOpportunityWorkbooksAsync(false, cancellationToken); + data.Workbooks = strategicOpportunityWorkbooks.Select(wb => new StrategicOpportunityWorkbookDto + { + OpportunityId = strategicOpportunity.OpportunityId, + WorkbookId = wb.WorkbookId, + Name = workbooks.FirstOrDefault(x => x.WorkbookId == wb.WorkbookId).Name + }).ToList(); + + return data; + } + + public async Task GetStrategicOpportunityGeneralInfo(long opportunityId, CancellationToken cancellationToken) + { + var strataId = _user.GetStrataId(); + + var chargeCode = await _centralDbContext.StrategicOpportunityChargeCodes + .Include(item => item.Stakeholders) + .Include(item => item.Measures) + .FirstOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == strataId, cancellationToken); + + if (chargeCode != null) return new StrategicOpportunityGeneralInfo(chargeCode); + + var gl = await _centralDbContext.StrategicOpportunityGLs + .Include(item => item.Stakeholders) + .Include(item => item.Measures) + .FirstOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == strataId, cancellationToken); + + if (gl != null) return new StrategicOpportunityGeneralInfo(gl); + + return null; + } + + public async Task ContainsAsync(long opportunityId, CancellationToken cancellationToken) + { + return await centralDbContext.StrategicOpportunities + .AnyAsync(x => x.StrataId == _user.GetStrataId() && x.OpportunityId == opportunityId, cancellationToken); + } + + public async Task ContainsGLAsync(long opportunityId, CancellationToken cancellationToken) + { + return await centralDbContext.StrategicOpportunityGLs + .AnyAsync(x => x.StrataId == _user.GetStrataId() && x.OpportunityId == opportunityId, cancellationToken); + } + + public async Task DeleteChargeCodeOpportunityAsync(long opportunityId, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunities.SingleOrDefaultAsync(item => item.OpportunityId == opportunityId + && item.StrataId == _user.GetStrataId(), cancellationToken); + + var strategy = _centralDbContext.Database.CreateExecutionStrategy(); + + var isDeleted = await strategy.ExecuteAsync(async () => + { + using var centralTransaction = await _centralDbContext.Database.BeginTransactionAsync(CancellationToken.None); + try + { + await _centralDbContext.StrategicOpportunityMeasures.ExecuteDeleteAsync(item => item.OpportunityId == opportunityId && item.StrataId == _user.GetStrataId(), CancellationToken.None); + + await _centralDbContext.StrategicOpportunityWorkbooks.ExecuteDeleteAsync(item => item.OpportunityId == opportunityId && item.StrataId == _user.GetStrataId(), CancellationToken.None); + + await _centralDbContext.Comments.ExecuteDeleteAsync(item => item.OpportunityId == opportunityId && item.StrataId == _user.GetStrataId(), CancellationToken.None); + + await _centralDbContext.StrategicOpportunityRampUps.ExecuteDeleteAsync(item => item.OpportunityId == opportunityId, CancellationToken.None); + + RemoveOpportunitySpecifications(opportunityId, cancellationToken); + _centralDbContext.StrategicOpportunities.Remove(strategicOpportunity); + + var isDeleted = await _centralDbContext.SaveChangesAsync(CancellationToken.None) > 0; + await centralTransaction.CommitAsync(CancellationToken.None); + return isDeleted; + } + catch (Exception ex) + { + logger.LogServiceError(ex); + await centralTransaction.RollbackAsync(CancellationToken.None); + throw; + } + }); + + if (isDeleted) + { + try + { + var queryBuilder = new StrategicOpportunityDetailQueryBuilder(); + var deleteSql = queryBuilder.BuildDeleteQuery(opportunityId); + await databaseContext.ExecuteCommandAsync(deleteSql); + await strategicItemDimensionSyncService.DeleteAsync(opportunityId); + } + catch (Exception ex) + { + logger.LogServiceError(ex, $"Error occurred while executing Snowflake cleanup for opportunity {opportunityId}."); + } + } + + return isDeleted; + } + + public async Task CreateOpportunityChargeCodeAsync(StrategicOpportunityChargeCodeDto opportunityDto, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + + try + { + var opportunity = opportunityDto.MapToStrategicOpportunityChargeCode(); + + var dbGuid = claimsPrincipalAccessor.GetDatabaseGuid(); + var strataId = await idServiceClient.GetStrataId(dbGuid, cancellationToken); + opportunity.StrataId = strataId; + + InitializeNewOpportunity(opportunity, opportunityDto, strataId); + + opportunity.Measures.Add(new StrategicOpportunityMeasure() { StrataId = strataId, MeasureGroup = MeasureGroup.System, MeasureType = MeasureType.CommittedCost, MeasureName = "CommittedCost" }); + opportunity.Measures.Add(new StrategicOpportunityMeasure() { StrataId = strataId, MeasureGroup = MeasureGroup.System, MeasureType = MeasureType.ActualCost, MeasureName = "ActualCost" }); + + opportunity.Workbooks = GetStrategicOpportunityWorkbooks(opportunityDto.WorkbookIds.ToList(), strataId); + + opportunity.LayoutJSON = JsonConvert.SerializeObject(opportunityDto.Layout); + opportunity.Status = (byte)StrategicOpportunityStatus.NewOpportunity; + + _centralDbContext.StrategicOpportunityChargeCodes.Add(opportunity); + + await _centralDbContext.SaveChangesAsync(cancellationToken); + + await opportunityRoleAssignmentService.AddPermissionsFromDefaultsAsync(opportunity.OpportunityId, cancellationToken); + + await strategicChargeCodeOpportunityDetailService.PopulateStrategicOpportunityDetails(opportunity, cancellationToken); + await strategicItemDimensionSyncService.UpsertAsync(opportunity, cancellationToken); + + return opportunity.OpportunityId; + } + catch (Exception ex) + { + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + public async Task UpdateOpportunityChargeCode(long opportunityId, StrategicOpportunityChargeCodeUpdateDto opportunityDto, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + + try + { + var dbGuid = claimsPrincipalAccessor.GetDatabaseGuid(); + var strataId = await idServiceClient.GetStrataId(dbGuid, cancellationToken); + + var strategicOpportunity = await _centralDbContext.StrategicOpportunityChargeCodes.FirstOrDefaultAsync(opp => opp.OpportunityId == opportunityId && opp.StrataId == strataId, cancellationToken); + if (strategicOpportunity == null) return 0; + + var trackingConfigChanged = UpdateOpportunityCore(strategicOpportunity, opportunityDto.Opportunity, strataId); + + strategicOpportunity.ChargeCodeHPath = opportunityDto.Opportunity.ChargeCodeHPath; + strategicOpportunity.ExplorationPopulationId = opportunityDto.Opportunity.ExplorationPopulationId; + strategicOpportunity.PatientPopulationHPath = opportunityDto.Opportunity.PatientPopulationHPath; + + _centralDbContext.StrategicOpportunityChargeCodes.Update(strategicOpportunity); + await _centralDbContext.SaveChangesAsync(cancellationToken); + + if (opportunityDto.ShouldPopulateOpportunityDetails) + await strategicChargeCodeOpportunityDetailService.PopulateStrategicOpportunityDetails(strategicOpportunity, cancellationToken); + else + { + if (trackingConfigChanged) + await strategicChargeCodeOpportunityDetailService.PopulateStrategicOpportunityGoalDetails(strategicOpportunity, DistributionStage.OpportunityChangeGoalPeriod, true, cancellationToken); + } + await strategicItemDimensionSyncService.UpsertAsync(strategicOpportunity, cancellationToken); + return strategicOpportunity.OpportunityId; + } + catch (Exception ex) + { + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + public async Task CreateOpportunityGeneralLedgerAsync(StrategicOpportunityGLDto opportunityDto, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + + try + { + var opportunity = opportunityDto.MapToStrategicOpportunityGL(); + + var dbGuid = claimsPrincipalAccessor.GetDatabaseGuid(); + var strataId = await idServiceClient.GetStrataId(dbGuid, cancellationToken); + opportunity.StrataId = strataId; + + InitializeNewOpportunity(opportunity, opportunityDto, strataId); + + _centralDbContext.StrategicOpportunityGLs.Add(opportunity); + await _centralDbContext.SaveChangesAsync(cancellationToken); + + await opportunityRoleAssignmentService.AddPermissionsFromDefaultsAsync(opportunity.OpportunityId, cancellationToken); + + await strategicGLOpportunityDetailService.PopulateStrategicOpportunityGLDetails(opportunity, cancellationToken); + + return opportunity.OpportunityId; + } + catch (Exception ex) + { + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + public async Task UpdateOpportunityGeneralLedger(long opportunityId, StrategicOpportunityGLUpdateDto opportunityDto, CancellationToken cancellationToken) + { + using var dbContext = await GetDbContext(cancellationToken); + + try + { + var dbGuid = claimsPrincipalAccessor.GetDatabaseGuid(); + var strataId = await idServiceClient.GetStrataId(dbGuid, cancellationToken); + + var strategicOpportunity = await _centralDbContext.StrategicOpportunityGLs.FirstOrDefaultAsync(opp => opp.OpportunityId == opportunityId && opp.StrataId == strataId, cancellationToken); + if (strategicOpportunity == null) return 0; + + var trackingConfigChanged = UpdateOpportunityCore(strategicOpportunity, opportunityDto.Opportunity, strataId); + + strategicOpportunity.AccountType = opportunityDto.Opportunity.AccountType; + strategicOpportunity.DepartmentHPath = opportunityDto.Opportunity.DepartmentHPath; + strategicOpportunity.RevenueAccountHPath = opportunityDto.Opportunity.RevenueAccountHPath; + strategicOpportunity.ExpenseAccountHPath = opportunityDto.Opportunity.ExpenseAccountHPath; + strategicOpportunity.StatisticAccountHPath = opportunityDto.Opportunity.StatisticAccountHPath; + strategicOpportunity.HasStatistic = opportunityDto.Opportunity.HasStatistic; + + _centralDbContext.StrategicOpportunityGLs.Update(strategicOpportunity); + await _centralDbContext.SaveChangesAsync(cancellationToken); + + if (opportunityDto.ShouldPopulateOpportunityDetails) + await strategicGLOpportunityDetailService.PopulateStrategicOpportunityGLDetails(strategicOpportunity, cancellationToken); + else if (trackingConfigChanged) + await strategicGLOpportunityDetailService.PopulateStrategicOpportunityGLGoalDetails(strategicOpportunity, DistributionStage.OpportunityChangeGoalPeriod, true, cancellationToken); + + return strategicOpportunity.OpportunityId; + } + catch (Exception ex) + { + logger.LogServiceError(ex, ex.Message); + throw; + } + } + + public async Task GetStrategicOpportunityGL(long opportunityId, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunityGLs.SingleOrDefaultAsync(opp => opp.OpportunityId == opportunityId, cancellationToken); + if (strategicOpportunity == null) return null; + + var data = new StrategicOpportunityGLEditData + { + Name = strategicOpportunity.Name, + OpportunityType = strategicOpportunity.OpportunityType, + Rollup1Id = strategicOpportunity.Rollup1Id, + Rollup2Id = strategicOpportunity.Rollup2Id, + Rollup3Id = strategicOpportunity.Rollup3Id, + Filters = strategicOpportunity.OpportunityFiltersJSON, + BaselineStartDate = strategicOpportunity.BaselineStartDate, + BaselineEndDate = strategicOpportunity.BaselineEndDate, + BaselineDistribution = strategicOpportunity.BaselineDistribution, + EstimatedTrackingDuration = strategicOpportunity.EstimatedTrackingDuration, + AccountType = strategicOpportunity.AccountType, + DepartmentHPath = strategicOpportunity.DepartmentHPath, + RevenueAccountHPath = strategicOpportunity.RevenueAccountHPath, + ExpenseAccountHPath = strategicOpportunity.ExpenseAccountHPath, + StatisticAccountHPath = strategicOpportunity.StatisticAccountHPath, + HasStatistic = strategicOpportunity.HasStatistic + }; + + var measures = await _centralDbContext.StrategicOpportunityMeasures.Where(x => x.OpportunityId == strategicOpportunity.OpportunityId).ToListAsync(cancellationToken); + data.Measures = measures.Select(m => m.MapToStrategicOpportunityMeasureDto()).ToList(); + + var strategicOpportunityWorkbooks = await _centralDbContext.StrategicOpportunityWorkbooks.Where(x => x.OpportunityId == strategicOpportunity.OpportunityId).ToListAsync(cancellationToken); + var allWorkbooks = await opportunityWorkbookService.GetAllOpportunityWorkbooksAsync(false, cancellationToken); + data.Workbooks = strategicOpportunityWorkbooks.Select(wb => new StrategicOpportunityWorkbookDto + { + OpportunityId = strategicOpportunity.OpportunityId, + WorkbookId = wb.WorkbookId, + Name = allWorkbooks.FirstOrDefault(x => x.WorkbookId == wb.WorkbookId)?.Name + }).ToList(); + + return data; + } + + public async Task DeleteGeneralLedgerOpportunityAsync(long opportunityId, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunities.SingleOrDefaultAsync(item => item.OpportunityId == opportunityId + && item.StrataId == _user.GetStrataId(), cancellationToken); + + var strategy = _centralDbContext.Database.CreateExecutionStrategy(); + + var isDeleted = await strategy.ExecuteAsync(async () => + { + using var centralTransaction = await _centralDbContext.Database.BeginTransactionAsync(CancellationToken.None); + try + { + await _centralDbContext.StrategicOpportunityMeasures.ExecuteDeleteAsync(item => item.OpportunityId == opportunityId && item.StrataId == _user.GetStrataId(), CancellationToken.None); + await _centralDbContext.StrategicOpportunityWorkbooks.ExecuteDeleteAsync(item => item.OpportunityId == opportunityId && item.StrataId == _user.GetStrataId(), CancellationToken.None); + await _centralDbContext.Comments.ExecuteDeleteAsync(item => item.OpportunityId == opportunityId && item.StrataId == _user.GetStrataId(), CancellationToken.None); + await _centralDbContext.StrategicOpportunityRampUps.ExecuteDeleteAsync(item => item.OpportunityId == opportunityId, CancellationToken.None); + + RemoveOpportunitySpecifications(opportunityId, cancellationToken); + _centralDbContext.StrategicOpportunities.Remove(strategicOpportunity); + + var isDeleted = await _centralDbContext.SaveChangesAsync(CancellationToken.None) > 0; + await centralTransaction.CommitAsync(CancellationToken.None); + return isDeleted; + } + catch (Exception ex) + { + logger.LogServiceError(ex); + await centralTransaction.RollbackAsync(CancellationToken.None); + throw; + } + }); + + if (isDeleted) + { + try + { + var glQueryBuilder = new Queries.StrategicOpportunityGLDetailQueryBuilder(); + var glDeleteSql = glQueryBuilder.BuildDeleteQuery(opportunityId); + await databaseContext.QueryAsync(glDeleteSql, $"{nameof(DeleteGeneralLedgerOpportunityAsync)}.Delete"); + await strategicItemDimensionSyncService.DeleteAsync(opportunityId); + } + catch (OperationCanceledException) + { + throw; + } + catch (DataException ex) + { + logger.LogServiceError(ex, $"Error occurred while executing Snowflake cleanup for opportunity {opportunityId}."); + } + } + + return isDeleted; + } + + public async Task NameIsValidAsync(string name, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + var isExist = await _centralDbContext.StrategicOpportunities.Where(item => item.StrataId == _user.GetStrataId()).AnyAsync(x => x.Name.ToLower() == name.ToLower(), cancellationToken); + return !isExist; + } + + public async Task GetStrategicOpportunityLayout(long opportunityId, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunities.FirstOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (strategicOpportunity != null && !string.IsNullOrEmpty(strategicOpportunity.LayoutJSON)) + { + return JsonConvert.DeserializeObject(strategicOpportunity.LayoutJSON); + } + else + { + return null; + } + } + + public async Task UpdateStrategicOpportunityLayout(long opportunityId, StrategicOpportunityLayout layout, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunities.SingleOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (strategicOpportunity != null) + { + strategicOpportunity.LayoutJSON = JsonConvert.SerializeObject(layout); + _centralDbContext.StrategicOpportunities.Update(strategicOpportunity); + + return await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + } + else return false; + } + + public async Task UpdateStrategicInitiative(long opportunityId, StrategicInitiativeUpdateDto updateInitiativeDto, CancellationToken cancellationToken) + { + var strategicInitiative = await _centralDbContext.StrategicOpportunityChargeCodes.SingleOrDefaultAsync(item => item.OpportunityId == opportunityId + && item.Status >= (byte)StrategicOpportunityStatus.NewInitiative && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (strategicInitiative != null) + { + strategicInitiative.Name = updateInitiativeDto.Name; + + var isYearMonthChanged = + new YearMonthStruct(strategicInitiative.TrackingStartDate) != new YearMonthStruct(updateInitiativeDto.TrackingStartDate) || + new YearMonthStruct(strategicInitiative.TrackingEndDate) != new YearMonthStruct(updateInitiativeDto.TrackingEndDate); + + if (isYearMonthChanged) + { + await strategicChargeCodeOpportunityDetailService.DeleteMeasuresDetails(strategicInitiative.OpportunityId, cancellationToken); + + strategicInitiative.TrackingStartDate = updateInitiativeDto.TrackingStartDate; + strategicInitiative.TrackingEndDate = updateInitiativeDto.TrackingEndDate; + SyncRampUpWithTrackingPeriodChange(strategicInitiative.OpportunityId, updateInitiativeDto.TrackingStartDate, updateInitiativeDto.TrackingEndDate); + await strategicChargeCodeOpportunityDetailService.PopulateStrategicOpportunityCommittedDetails(strategicInitiative, DistributionStage.InitiativeChangeTrackingPeriod, false, cancellationToken); + } + var saveResult = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + + if (isYearMonthChanged && saveResult) + { + if (strategicInitiative.Status != (byte)StrategicOpportunityStatus.NewInitiative) + backgroundJobClient.Enqueue((job) => job.RunOpportunityTracking(strategicInitiative.StrataId, strategicInitiative.OpportunityId, CancellationToken.None)); + } + + if (saveResult) + await strategicItemDimensionSyncService.UpsertAsync(strategicInitiative, cancellationToken); + + return saveResult; + } + + var glInitiative = await _centralDbContext.StrategicOpportunityGLs.SingleOrDefaultAsync(item => item.OpportunityId == opportunityId + && item.Status >= (byte)StrategicOpportunityStatus.NewInitiative && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (glInitiative != null) + { + glInitiative.Name = updateInitiativeDto.Name; + + var isYearMonthChanged = + new YearMonthStruct(glInitiative.TrackingStartDate) != new YearMonthStruct(updateInitiativeDto.TrackingStartDate) || + new YearMonthStruct(glInitiative.TrackingEndDate) != new YearMonthStruct(updateInitiativeDto.TrackingEndDate); + + if (isYearMonthChanged) + { + glInitiative.TrackingStartDate = updateInitiativeDto.TrackingStartDate; + glInitiative.TrackingEndDate = updateInitiativeDto.TrackingEndDate; + SyncRampUpWithTrackingPeriodChange(glInitiative.OpportunityId, updateInitiativeDto.TrackingStartDate, updateInitiativeDto.TrackingEndDate); + await strategicGLOpportunityDetailService.PopulateStrategicOpportunityCommittedDetails(glInitiative, DistributionStage.InitiativeChangeTrackingPeriod, false, cancellationToken); + } + var saveResult = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + + if (isYearMonthChanged && glInitiative.Status != (byte)StrategicOpportunityStatus.NewInitiative && saveResult) + backgroundJobClient.Enqueue((job) => job.RunOpportunityTracking(glInitiative.StrataId, glInitiative.OpportunityId, CancellationToken.None)); + + if (saveResult) + await strategicItemDimensionSyncService.UpsertAsync(glInitiative, cancellationToken); + + return saveResult; + } + + return false; + } + public async Task UpdateRoles(long opportunityId, StrategicRolesUpdate updateRolesDto, CancellationToken cancellationToken) + { + var strategicEntity = await _centralDbContext.StrategicOpportunities.SingleOrDefaultAsync(item => item.OpportunityId == opportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (strategicEntity != null) + { + var strategy = _centralDbContext.Database.CreateExecutionStrategy(); + + var isUpdated = await strategy.ExecuteAsync(async () => + { + using var centralTransaction = await _centralDbContext.Database.BeginTransactionAsync(cancellationToken); + try + { + await _centralDbContext.StakeholdersLinks + .Where(link => link.StrategicOpportunityId == opportunityId) + .ExecuteDeleteAsync(cancellationToken); + + strategicEntity.CostLeaderGuid = updateRolesDto.CostLeaderGuid; + strategicEntity.OwnerGuid = updateRolesDto.OwnerGuid; + strategicEntity.ExecutiveSponsorGuid = updateRolesDto.ExecutiveSponsorGuid; + strategicEntity.TeamMembers = updateRolesDto.TeamMembers; + strategicEntity.Stakeholders = updateRolesDto.Stakeholders + .Select(s => new StrategicStakeholdersLink(strategicEntity.OpportunityId, s)) + .ToList(); + + _centralDbContext.StrategicOpportunities.Update(strategicEntity); + + var isUpdated = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + await centralTransaction.CommitAsync(CancellationToken.None); + if (isUpdated) + await strategicItemDimensionSyncService.UpsertAsync(strategicEntity, cancellationToken); + return isUpdated; + } + catch (Exception ex) + { + logger.LogServiceError(ex); + await centralTransaction.RollbackAsync(CancellationToken.None); + throw; + } + }); + + return isUpdated; + } + else return false; + } + + public async Task UpdateStrategicOpportunityStatus(long opportunityId, StrategicOpportunityUpdateStatusDto updateStatusDto, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunityChargeCodes.SingleOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (strategicOpportunity != null) + { + strategicOpportunity.Status = updateStatusDto.Status; + if (updateStatusDto.Status == (byte)StrategicOpportunityStatus.NewInitiative) + { + strategicOpportunity.TrackingStartDate = updateStatusDto.TrackingStartDate; + strategicOpportunity.TrackingEndDate = updateStatusDto.TrackingEndDate; + SyncRampUpWithTrackingPeriodChange(strategicOpportunity.OpportunityId, updateStatusDto.TrackingStartDate, updateStatusDto.TrackingEndDate); + await strategicChargeCodeOpportunityDetailService.DeleteMeasuresDetails(strategicOpportunity.OpportunityId, cancellationToken); + await strategicChargeCodeOpportunityDetailService.PopulateStrategicOpportunityCommittedDetails(strategicOpportunity, DistributionStage.FromOpportunityToInitiative, false, cancellationToken); + } + else if (updateStatusDto.Status == (byte)StrategicOpportunityStatus.NewOpportunity) + { + strategicOpportunity.LastRunTrackingDate = null; + strategicOpportunity.LastRunTrackingStatus = RunTrackingStatus.None; + strategicOpportunity.TrackingStartDate = default; + strategicOpportunity.TrackingEndDate = default; + strategicOpportunity.ProgressToDateActual = 0; + strategicOpportunity.ProgressToDateCommitted = 0; + await strategicChargeCodeOpportunityDetailService.DeleteMeasuresDetails(strategicOpportunity.OpportunityId, cancellationToken); + await strategicChargeCodeOpportunityDetailService.PopulateStrategicOpportunityGoalDetails(strategicOpportunity, DistributionStage.FromInitiativeToOpportunity, false, cancellationToken); + } + + var saveResult = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + + if (saveResult) + { + if (updateStatusDto.Status == (byte)StrategicOpportunityStatus.NewInitiative || updateStatusDto.Status == (byte)StrategicOpportunityStatus.CompleteMonitoring) + { + backgroundJobClient.Enqueue((job) => job.RunOpportunityUpdateMeasuresAverage(strategicOpportunity.StrataId, strategicOpportunity.OpportunityId, CancellationToken.None)); + } + await strategicItemDimensionSyncService.UpsertAsync(strategicOpportunity, cancellationToken); + } + + return saveResult; + } + + var glOpportunity = await _centralDbContext.StrategicOpportunityGLs.SingleOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + + if (glOpportunity != null) + { + glOpportunity.Status = updateStatusDto.Status; + if (updateStatusDto.Status == (byte)StrategicOpportunityStatus.NewInitiative) + { + glOpportunity.TrackingStartDate = updateStatusDto.TrackingStartDate; + glOpportunity.TrackingEndDate = updateStatusDto.TrackingEndDate; + SyncRampUpWithTrackingPeriodChange(glOpportunity.OpportunityId, updateStatusDto.TrackingStartDate, updateStatusDto.TrackingEndDate); + await strategicGLOpportunityDetailService.PopulateStrategicOpportunityCommittedDetails(glOpportunity, DistributionStage.FromOpportunityToInitiative, false, cancellationToken); + } + else if (updateStatusDto.Status == (byte)StrategicOpportunityStatus.NewOpportunity) + { + glOpportunity.LastRunTrackingDate = null; + glOpportunity.LastRunTrackingStatus = RunTrackingStatus.None; + glOpportunity.TrackingStartDate = default; + glOpportunity.TrackingEndDate = default; + glOpportunity.ProgressToDateActual = 0; + glOpportunity.ProgressToDateCommitted = 0; + await strategicGLOpportunityDetailService.PopulateStrategicOpportunityGLGoalDetails(glOpportunity, DistributionStage.FromInitiativeToOpportunity, false, cancellationToken); + } + var glSaveResult = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + if (glSaveResult) await strategicItemDimensionSyncService.UpsertAsync(glOpportunity, cancellationToken); + return glSaveResult; + } + + return false; + } + + public async Task UpdateStrategicOpportunityRampUpsAsync(long opportunityId, List rampUps, CancellationToken cancellationToken) + { + // Remove existing ramp-ups for the opportunity + var existingRampUps = _centralDbContext.StrategicOpportunityRampUps.Where(r => r.OpportunityId == opportunityId); + _centralDbContext.StrategicOpportunityRampUps.RemoveRange(existingRampUps); + + // Add new ramp-ups + var newRampUps = rampUps.Select(dto => new StrategicOpportunityRampUp + { + OpportunityId = dto.OpportunityId, + MonthNumber = dto.MonthNumber, + Percentage = dto.Percentage + }).ToList(); + await _centralDbContext.StrategicOpportunityRampUps.AddRangeAsync(newRampUps, cancellationToken); + + var opportunity = await _centralDbContext.StrategicOpportunityChargeCodes.SingleOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + if (opportunity != null) { + var newRampUpValues = newRampUps.OrderBy(r => r.MonthNumber).Select(r => r.Percentage).ToArray(); + if (opportunity.Status >= (byte)StrategicOpportunityStatus.NewInitiative) + { + var oldRampUpValues = existingRampUps.OrderBy(r => r.MonthNumber).Select(r => r.Percentage).ToArray(); + await strategicChargeCodeOpportunityDetailService.UpdateDetailCommittedByRampUp(opportunity, oldRampUpValues, newRampUpValues, false, cancellationToken); + } + else + { + await strategicChargeCodeOpportunityDetailService.PopulateDetailGoalSummary(opportunity, newRampUpValues, false, cancellationToken); + } + } + + var glOpportunity = await _centralDbContext.StrategicOpportunityGLs.SingleOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + if (glOpportunity != null) + { + var newRampUpValues = newRampUps.OrderBy(r => r.MonthNumber).Select(r => r.Percentage).ToArray(); + if (glOpportunity.Status >= (byte)StrategicOpportunityStatus.NewInitiative) + { + var oldRampUpValues = existingRampUps.OrderBy(r => r.MonthNumber).Select(r => r.Percentage).ToArray(); + await strategicGLOpportunityDetailService.UpdateDetailCommittedByRampUp(glOpportunity, oldRampUpValues, newRampUpValues, false, cancellationToken); + } + else + { + await strategicGLOpportunityDetailService.PopulateGLDetailGoalSummary(glOpportunity, newRampUpValues, false, cancellationToken); + } + } + + return await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + } + + public async Task ResetStrategicOpportunityRampUps(long opportunityId, CancellationToken cancellationToken) + { + var rampUps = await _centralDbContext.StrategicOpportunityRampUps + .Where(r => r.OpportunityId == opportunityId) + .ToListAsync(cancellationToken); + + if (rampUps != null && rampUps.Count != 0) + { + foreach (var rampUp in rampUps) + rampUp.Percentage = 1; + + return await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + } + return false; + } + + public async Task GetStrategicOpportunityDrawerDetails(long opportunityId, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunityChargeCodes.FirstOrDefaultAsync(item => opportunityId == item.OpportunityId && item.StrataId == _user.GetStrataId(), cancellationToken); + if (strategicOpportunity == null) return null; + + var chargeCodeDimension = new List + { + StrategicOpportunityDetailDimension.ChargeCode + }; + + var dicDimensionSql = StrategicOpportunityServiceQuery.GetDetailQuery(chargeCodeDimension, true); + if (dicDimensionSql.Count == 0) return null; + + var strategicOpportunityItem = new List() + { + new() + { + PatientPopulationHPath = strategicOpportunity.PatientPopulationHPath, + ExplorationPopulationId = strategicOpportunity.ExplorationPopulationId + } + }; + + await SetPopulationName(strategicOpportunityItem, cancellationToken); + var populations = strategicOpportunityItem.FirstOrDefault().Population.Split(", ").ToList(); + + var data = new StrategicOpportunityDrawerDetails() + { + PopulationName = string.IsNullOrEmpty(strategicOpportunity.PatientPopulationHPath) ? "Exploration Patient Population" : "Patient Population", + Populations = populations, + Filters = strategicOpportunity.OpportunityFiltersJSON, + BaselinePeriod = $"{strategicOpportunity.BaselineStartDate:MM/dd/yyyy} to {strategicOpportunity.BaselineEndDate:MM/dd/yyyy}", + TrackingStartDate = strategicOpportunity.TrackingStartDate, + TrackingEndDate = strategicOpportunity.TrackingEndDate, + EstimatedTrackingDuration = strategicOpportunity.EstimatedTrackingDuration + }; + + var querySelect = String.Join(Environment.NewLine, dicDimensionSql.Values).SnowflakeSyntax(); + var queryOneParams = new + { + OpportunityId = opportunityId, + IsBaseline = strategicOpportunity.Status < (byte)StrategicOpportunityStatus.NewInitiative ? (bool?)null : false + }; + + try + { + var results = await databaseContext.QueryAsync>(querySelect, "Charge Codes", queryOneParams); + data.ChargeCodes = results.ToDictionary(x => x.Key.ToString(), x => x.Value); + } + catch (Exception ex) + { + logger.LogServiceError(ex, info: new Dictionary { { "query", querySelect }, { "queryParams", queryOneParams } }); + throw; + } + + return data; + } + + + public async Task CompleteInitiative(long opportunityId, StrategicOpportunityCompleteInitiativeData completeInitiativeData, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunityChargeCodes.FindAsync([opportunityId], cancellationToken: cancellationToken); + if (strategicOpportunity == null) return false; + + if (completeInitiativeData.IsThresholdMonitoring) + { + strategicOpportunity.Status = (byte)StrategicOpportunityStatus.CompleteMonitoring; + var primaryMeasure = await _centralDbContext.StrategicOpportunityMeasures + .Where(pm => pm.OpportunityId == opportunityId && pm.MeasureGroup == MeasureGroup.Primary) + .SingleOrDefaultAsync(cancellationToken); + + if (primaryMeasure != null) + primaryMeasure.AcceptableThreshold = completeInitiativeData.AcceptableThreshold; + + await strategicChargeCodeOpportunityDetailService.UpdateMeasuresAverage(strategicOpportunity, cancellationToken); + } else + { + strategicOpportunity.Status = (byte)StrategicOpportunityStatus.Complete; + } + + await _centralDbContext.SaveChangesAsync(cancellationToken); + await strategicItemDimensionSyncService.UpsertAsync(strategicOpportunity, cancellationToken); + return true; + } + + public async Task> GetStrategicOpportunityMeasures(long opportunityId, CancellationToken cancellationToken) + { + var measures = await _centralDbContext.StrategicOpportunityMeasures.Where(measure => measure.OpportunityId == opportunityId).ToListAsync(cancellationToken); + + return measures.Any() ? measures.Select(m => new StrategicOpportunityMeasureDto() { MeasureId = m.MeasureId, MeasureName = m.MeasureName }).ToList() + : new List(); + } + + public async Task GetStrategicOpportunityPrimaryMeasure(long opportunityId, CancellationToken cancellationToken) + { + var strategicOpportunity = await _centralDbContext.StrategicOpportunityMeasures + .Where(pm => pm.OpportunityId == opportunityId && pm.MeasureGroup == MeasureGroup.Primary) + .FirstOrDefaultAsync(cancellationToken); + + if (strategicOpportunity == null) return null; + + return strategicOpportunity.MapToStrategicOpportunityMeasureDto(); + } + + + public async Task> GetStrategicOpportunityRampUpsAsync(long opportunityId, CancellationToken cancellationToken) + { + var rampUps = await _centralDbContext.StrategicOpportunityRampUps + .Where(r => r.OpportunityId == opportunityId) + .OrderBy(r => r.MonthNumber) + .Select(r => new StrategicOpportunityRampUpDto + { + OpportunityId = r.OpportunityId, + MonthNumber = r.MonthNumber, + Percentage = r.Percentage + }) + .ToListAsync(cancellationToken); + return rampUps; + } + + public async Task RunTrackingTask(long opportunityId, CancellationToken cancellationToken) + { + var opportunityChargeCode = await _centralDbContext.StrategicOpportunityChargeCodes.FindAsync([opportunityId], cancellationToken: cancellationToken); + if (opportunityChargeCode != null) + { + backgroundJobClient.Enqueue((job) => job.RunOpportunityTracking(opportunityChargeCode.StrataId, opportunityId, cancellationToken)); + opportunityChargeCode.Status = (byte)StrategicOpportunityStatus.Tracking; + return await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + } + + var opportunityGL = await _centralDbContext.StrategicOpportunityGLs.FindAsync([opportunityId], cancellationToken: cancellationToken); + if (opportunityGL != null) + { + backgroundJobClient.Enqueue((job) => job.RunOpportunityTracking(opportunityGL.StrataId, opportunityId, cancellationToken)); + opportunityGL.Status = (byte)StrategicOpportunityStatus.Tracking; + var saveResult = await _centralDbContext.SaveChangesAsync(cancellationToken) > 0; + if (saveResult) await strategicItemDimensionSyncService.UpsertAsync(opportunityGL, cancellationToken); + return saveResult; + } + + return false; + } + + public async Task> GetStrategicOpportunityMeasureDetails(long measureId, CancellationToken cancellationToken) + { + var measure = await _centralDbContext.StrategicOpportunityMeasures.Where(measure => measure.MeasureId == measureId).SingleOrDefaultAsync(cancellationToken); + + if (measure == null) return null; + + var querySelect = "SELECT * FROM cci.StrategicOpportunityMeasureDetail WHERE MeasureId = :MeasureId"; + var queryOneParams = new + { + MeasureId = measureId + }; + + try + { + var data = await databaseContext.QueryAsync(querySelect, "Strategic Opportunity Measures", queryOneParams); + return data.ToList(); + } + catch (Exception ex) + { + logger.LogServiceError(ex, info: new Dictionary { { "query", querySelect }, { "queryParams", queryOneParams } }); + throw; + } + } + + public async Task CopyStrategicOpportunity(long opportunityId, string opportunityName, CancellationToken cancellationToken) + { + var ccOpportunity = await _centralDbContext.StrategicOpportunityChargeCodes.FindAsync([opportunityId], cancellationToken: cancellationToken); + var glOpportunity = ccOpportunity == null + ? await _centralDbContext.StrategicOpportunityGLs.FindAsync([opportunityId], cancellationToken: cancellationToken) + : null; + + if (ccOpportunity == null && glOpportunity == null) return 0; + + var isGL = glOpportunity != null; + var strategy = _centralDbContext.Database.CreateExecutionStrategy(); + StrategicOpportunity newOpportunity = null; + + var copiedOpportunityId = await strategy.ExecuteAsync(async () => + { + using var copyOpportunityTransaction = await _centralDbContext.Database.BeginTransactionAsync(cancellationToken); + try + { + if (isGL) + { + var newGL = new StrategicOpportunityGL(glOpportunity, opportunityName); + newGL.LayoutJSON = glOpportunity.LayoutJSON; + await _centralDbContext.StrategicOpportunityGLs.AddAsync(newGL, cancellationToken); + newOpportunity = newGL; + } + else + { + var newCC = new StrategicOpportunityChargeCode(ccOpportunity, opportunityName); + + // set default layout json columns for new opportunity + var layoutJson = JObject.Parse(newCC.LayoutJSON); + layoutJson["Columns"] = new JArray{ "encounters", "encountersMonth", "unitsMonth", "unitsPerEncounter", "costPerEncounter", "costPerUnit", "costMonth", "goalUnitsMonth", + "goalUnitsPerEncounter", "goalCostPerEncounter", "goalCostPerUnit", "goalCostMonth", "units", "cost", "goalUnits", "goalCost", "totalImprovement" }; + newCC.LayoutJSON = JsonConvert.SerializeObject(layoutJson); + + await _centralDbContext.StrategicOpportunityChargeCodes.AddAsync(newCC, cancellationToken); + newOpportunity = newCC; + } + + await _centralDbContext.SaveChangesAsync(cancellationToken); + long newOpportunityId = newOpportunity.OpportunityId; + + // copy measures + newOpportunity.Measures = []; + var strategicOpportunityMeasures = _centralDbContext.StrategicOpportunityMeasures.Where(opp => opp.OpportunityId == opportunityId); + foreach (var measure in strategicOpportunityMeasures) + { + var newMeasure = new StrategicOpportunityMeasure() + { + OpportunityId = newOpportunityId, + MeasureType = measure.MeasureType, + MeasureName = measure.MeasureName, + DesiredResult = measure.DesiredResult, + AcceptableThreshold = measure.AcceptableThreshold, + FiltersJSON = measure.FiltersJSON, + MeasureGroup = measure.MeasureGroup, + StrataId = measure.StrataId + }; + newOpportunity.Measures.Add(newMeasure); + } + + // copy workbooks + newOpportunity.Workbooks = []; + var strategicOpportunityWorkbooks = _centralDbContext.StrategicOpportunityWorkbooks.Where(x => x.OpportunityId == opportunityId); + foreach (var workbook in strategicOpportunityWorkbooks) + { + var newWorkbook = new StrategicOpportunityWorkbook() + { + OpportunityId = newOpportunityId, + WorkbookId = workbook.WorkbookId, + StrataId = workbook.StrataId + }; + newOpportunity.Workbooks.Add(newWorkbook); + } + + // copy stakeholders + var stakeholders = _centralDbContext.StakeholdersLinks.Where(m => m.StrategicOpportunityId == opportunityId); + if (stakeholders.Any()) + { + var newStakeholders = stakeholders.Select(s => new StrategicStakeholdersLink(newOpportunityId, s.UserGuid)).ToList(); + await _centralDbContext.StakeholdersLinks.AddRangeAsync(newStakeholders, cancellationToken); + } + + // copy user roles + await opportunityRoleAssignmentService.CopyTo(opportunityId, newOpportunityId, cancellationToken); + + await _centralDbContext.SaveChangesAsync(cancellationToken); + await copyOpportunityTransaction.CommitAsync(CancellationToken.None); + return newOpportunityId; + } + catch (Exception ex) + { + logger.LogServiceError(ex); + await copyOpportunityTransaction.RollbackAsync(CancellationToken.None); + throw; + } + }); + + if (copiedOpportunityId > 0) + { + if (isGL) + await strategicGLOpportunityDetailService.PopulateStrategicOpportunityGLDetails(newOpportunity as StrategicOpportunityGL, cancellationToken); + else + await strategicChargeCodeOpportunityDetailService.PopulateStrategicOpportunityDetails(newOpportunity as StrategicOpportunityChargeCode, cancellationToken); + await strategicItemDimensionSyncService.UpsertAsync(newOpportunity, cancellationToken); + } + + return copiedOpportunityId; + } + + private void RemoveOpportunitySpecifications(long opportunityId, CancellationToken cancellationToken) where T : StrategicOpportunity + { + var specifications = _centralDbContext.Set().Where(item => item.OpportunityId == opportunityId && item.StrataId == _user.GetStrataId()); + _centralDbContext.Set().RemoveRange(specifications); + } + + private string GetRollupName(IEnumerable rollups, int rollupId, int index) + { + var rollup = rollups.Skip(index).FirstOrDefault(); + if (rollup != null) + return rollup.InitiativeRollupValues.FirstOrDefault(r => r.InitiativeRollupValueId == rollupId)?.Value ?? string.Empty; + + return string.Empty; + } + + private string GetOpportunityTypeName(StrategicOpportunityTypes opportunityType) + { + return opportunityType switch + { + StrategicOpportunityTypes.ChargeCode => "Charge Code", + StrategicOpportunityTypes.GeneralLedger => "General Ledger", + _ => string.Empty + }; + } + + // We should use the existing StrategicOpportunityStatus enum + private string GetStatusName(byte status) + { + return status switch + { + (byte)StrategicOpportunityStatus.NewOpportunity or (byte)StrategicOpportunityStatus.NewInitiative => "New", + (byte)StrategicOpportunityStatus.InValidation => "In Validation", + (byte)StrategicOpportunityStatus.PendingApproval => "Pending Approval", + (byte)StrategicOpportunityStatus.Tracking => "Tracking", + (byte)StrategicOpportunityStatus.Complete => "Complete", + (byte)StrategicOpportunityStatus.CompleteMonitoring => "Complete Monitoring", + _ => string.Empty + }; + } + + private async Task SetPopulationName(List results, CancellationToken cancellationToken) where T : GeneralStrategicOpportunityItem + { + var explorationIds = results.Where(r => r.ExplorationPopulationId > 0).Select(r => r.ExplorationPopulationId).Distinct().ToList(); + var explorations = await _centralDbContext.ExplorationPopulations + .Where(ep => explorationIds.Contains(ep.ExplorationPopulationId)).ToDictionaryAsync(ep => ep.ExplorationPopulationId, ep => ep.Name, cancellationToken); + var dimension = await schemaServiceClient.GetDimensionByGlobalIdAsync("Clinical Indicator"); + var hPaths = results.Where(r => r.ExplorationPopulationId == 0 && !string.IsNullOrEmpty(r.PatientPopulationHPath)).SelectMany(r => r.PatientPopulationHPath.Split(",")).Distinct(); + var members = new List(); + foreach (var codeHpaths in hPaths.Batch(250)) + { + members.AddRange(await schemaServiceClient.GetHierarchyNodesFromHierarchyPathsAsync(dimension.DimensionGuid, codeHpaths, cancellationToken)); + } + foreach (var item in results) + { + item.Population = item.ExplorationPopulationId > 0 + ? explorations.FirstOrDefault(e => e.Key == item.ExplorationPopulationId).Value + : string.Join(", ", members.Where(m => item.PatientPopulationHPath.Split(",").Contains(m.Path)).Select(m => m.Name)); + } + } + + private async Task> GetDimensionMembersByHierarchyPaths(string dimensionGlobalId, List hierarchyPaths, CancellationToken cancellationToken) + { + var dimension = await schemaServiceClient.GetDimensionByGlobalIdAsync(dimensionGlobalId, cancellationToken); + var allMembers = new List(); + + foreach (var batch in hierarchyPaths.Batch(250)) + { + var members = await schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(dimension.DimensionGuid, batch, cancellationToken); + foreach (var memberBatch in members.Batch(250)) + { + var ids = memberBatch.Select(m => int.Parse(m.Id)).ToList(); + var filteredMembers = await schemaServiceClient.GetFilteredDimensionMembersByGlobalIdAsync(dimensionGlobalId, ids, cancellationToken); + allMembers.AddRange(filteredMembers); + } + } + + return allMembers; + } + + private static List GetStrategicOpportunityWorkbooks(List workbookIds, int strataId) + { + var workbooks = new List(); + foreach (var workbookId in workbookIds) + { + var workbook = new StrategicOpportunityWorkbook() + { + WorkbookId = workbookId, + StrataId = strataId + }; + workbooks.Add(workbook); + } + return workbooks; + } + + private void InitializeNewOpportunity(StrategicOpportunity entity, StrategicOpportunityDto dto, int strataId) + { + entity.Measures = dto.Measures.Select(measureDto => + { + var measure = measureDto.MapToStrategicOpportunityMeasure(); + measure.StrataId = strataId; + return measure; + }).ToList(); + entity.Measures.Add(new StrategicOpportunityMeasure() { StrataId = strataId, MeasureGroup = MeasureGroup.System, MeasureType = MeasureType.CommittedCost }); + entity.Measures.Add(new StrategicOpportunityMeasure() { StrataId = strataId, MeasureGroup = MeasureGroup.System, MeasureType = MeasureType.ActualCost }); + entity.Workbooks = GetStrategicOpportunityWorkbooks(dto.WorkbookIds.ToList(), strataId); + entity.LayoutJSON = JsonConvert.SerializeObject(dto.Layout); + entity.Status = (byte)StrategicOpportunityStatus.NewOpportunity; + } + + // Returns true if tracking config changed (callers use to decide partial repopulation) + private bool UpdateOpportunityCore(StrategicOpportunity entity, StrategicOpportunityDto dto, int strataId) + { + var trackingConfigChanged = + entity.EstimatedTrackingDuration != dto.EstimatedTrackingDuration || + entity.BaselineDistribution != dto.BaselineDistribution; + + entity.Name = dto.Name; + entity.Rollup1Id = dto.Rollup1Id; + entity.Rollup2Id = dto.Rollup2Id; + entity.Rollup3Id = dto.Rollup3Id; + entity.OpportunityType = dto.OpportunityType; + entity.OpportunityFiltersJSON = dto.OpportunityFiltersJSON; + entity.BaselineType = dto.BaselineType; + entity.BaselineDistribution = dto.BaselineDistribution; + entity.BaselineStartDate = dto.BaselineStartDate; + entity.BaselineEndDate = dto.BaselineEndDate; + entity.EstimatedTrackingDuration = dto.EstimatedTrackingDuration; + + var secondaryMeasures = _centralDbContext.StrategicOpportunityMeasures + .Where(m => m.OpportunityId == entity.OpportunityId && m.MeasureGroup != MeasureGroup.System); + _centralDbContext.StrategicOpportunityMeasures.RemoveRange(secondaryMeasures); + entity.Measures = dto.Measures.Select(x => x.MapToStrategicOpportunityMeasure()).ToList(); + + var existingWorkbooks = _centralDbContext.StrategicOpportunityWorkbooks + .Where(w => w.OpportunityId == entity.OpportunityId); + _centralDbContext.StrategicOpportunityWorkbooks.RemoveRange(existingWorkbooks); + entity.Workbooks = GetStrategicOpportunityWorkbooks(dto.WorkbookIds.ToList(), strataId); + + return trackingConfigChanged; + } + + private void SyncRampUpWithTrackingPeriodChange(long opportunityId, DateOnly start, DateOnly end) + { + int monthsDifference = DateExtensions.GetMonthDifference(start, end, true); + var existingRampUps = _centralDbContext.StrategicOpportunityRampUps.Where(r => r.OpportunityId == opportunityId && r.MonthNumber > monthsDifference); + _centralDbContext.StrategicOpportunityRampUps.RemoveRange(existingRampUps); + } + + private async Task> GetAllStrategicChargeCodes(Func filterPredicate, CancellationToken cancellationToken) + { + var strataId = _user.GetStrataId(); + var sdtUser = _user.IsSdtEmployee(); + var userGroups = _user.Claims + .Where(c => c.Type == JwtClaimTypes.Role) + .Select(c => c.Value); + var chargeCodes = await _centralDbContext.StrategicOpportunityChargeCodes.ToListAsync(cancellationToken); + var result = chargeCodes.Where(item => filterPredicate(item) && item.StrataId == strataId); + + if (!sdtUser) + { + var roleAssignments = await _centralDbContext.OpportunityRoleAssignments.Where(r => r.StrataId == strataId && (r.IdentityGuid.Equals(_user.GetUserGuid()) || userGroups.Contains(r.IdentityName))).ToListAsync(cancellationToken); + + result = result.Where(o => + roleAssignments.Any(r => + r.OpportunityId == o.OpportunityId && + (r.IsRead || r.IsWrite || r.IsSecureWrite))); + } + + return result.ToList(); + } + + private async Task> GetAllStrategicGLOpportunities(Func filterPredicate, CancellationToken cancellationToken) + { + var strataId = _user.GetStrataId(); + var sdtUser = _user.IsSdtEmployee(); + var userGroups = _user.Claims + .Where(c => c.Type == JwtClaimTypes.Role) + .Select(c => c.Value); + var glOpportunities = await _centralDbContext.StrategicOpportunityGLs.ToListAsync(cancellationToken); + var result = glOpportunities.Where(item => filterPredicate(item) && item.StrataId == strataId); + + if (!sdtUser) + { + var roleAssignments = await _centralDbContext.OpportunityRoleAssignments.Where(r => r.StrataId == strataId && (r.IdentityGuid.Equals(_user.GetUserGuid()) || userGroups.Contains(r.IdentityName))).ToListAsync(cancellationToken); + + result = result.Where(o => + roleAssignments.Any(r => + r.OpportunityId == o.OpportunityId && + (r.IsRead || r.IsWrite || r.IsSecureWrite))); + } + + return result.ToList(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityServiceQuery.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityServiceQuery.cs new file mode 100644 index 0000000..4ccdcba --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityServiceQuery.cs @@ -0,0 +1,58 @@ +using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; +using Strata.ContinuousImprovement.Biz.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + internal class StrategicOpportunityServiceQuery + { + private const string KEY_SEPARATOR = "|"; + + public static Dictionary GetDetailQuery(IEnumerable dimensions, bool onlyDimensions, string queryWhere = null) + { + var dicSql = new Dictionary(); + var assembly = Assembly.GetExecutingAssembly(); + var queryDetailList = ApplyQueryWhere(queryWhere, assembly.ReadResource("StrategicOpportunityDetailCTE.sql")); + var sql = string.Empty; + var dimensionColumnNames = new List(); + foreach (var dimension in dimensions) + { + if (StrategicOpportunityService.DimensionsConfig.TryGetValue(dimension, out var dimensionConfig)) + { + sql = ApplyQueryListOfEncounters(queryDetailList, assembly.ReadResource(dimensionConfig.SqlFile)); + dicSql.Add(dimension.ToString(), sql); + dimensionColumnNames.Add(dimensionConfig.ColumnName); + } + } + + if (!onlyDimensions) + { + var sqlSelect = @$" + -- {{queryDetailList}} + + SELECT + {(dimensionColumnNames.Count != 0 ? $"CONCAT({string.Join($", '{KEY_SEPARATOR}', ", dimensionColumnNames.Select(x => $"REPLACE(COALESCE(CAST({x} AS VARCHAR), ''), '{KEY_SEPARATOR}', '')"))})" : "''")} AS Key, + IsBaseline, + SUM(Units) AS Units, + SUM(Cost) AS Cost, + SUM(GoalUnits) AS GoalUnits, + SUM(GoalCost) AS GoalCost + FROM DetailList dl + GROUP BY IsBaseline {(dimensionColumnNames.Count != 0 ? $", {string.Join(", ", dimensionColumnNames)}" : "")}; + "; + sql = ApplyQueryListOfEncounters(queryDetailList, sqlSelect); + dicSql.Add("detail", sql); + } + + return dicSql; + } + + private static string ApplyQueryListOfEncounters(string queryDetailList, string sql) => sql.Replace("-- {queryDetailList}", queryDetailList); + private static string ApplyQueryWhere(string queryWhere, string sql) => sql.Replace("-- {queryWhere}", string.IsNullOrEmpty(queryWhere) ? string.Empty : queryWhere); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityWorkbook.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityWorkbook.cs new file mode 100644 index 0000000..55cdc1b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicOpportunityWorkbook.cs @@ -0,0 +1,18 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Table("strategic_opportunity_workbook")] + public class StrategicOpportunityWorkbook : IOpportunityWorkbook, IWithStrataId + { + public long OpportunityId { get; set; } + public int WorkbookId { get; set; } + public int StrataId { get; set; } + + public StrategicOpportunity Opportunity { get; set; } + [NotMapped] + public string Name { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicStakeholdersLink.cs b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicStakeholdersLink.cs new file mode 100644 index 0000000..dadeb6f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/StrategicOpportunities/StrategicStakeholdersLink.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.StrategicOpportunities +{ + [Table("strategic_stakeholders_link")] + public class StrategicStakeholdersLink + { + public long StrategicOpportunityId { get; set; } + public Guid UserGuid { get; set; } + + public StrategicOpportunity Opportunity { get; set; } + + public StrategicStakeholdersLink(long strategicOpportunityId, Guid userGuid) + { + StrategicOpportunityId = strategicOpportunityId; + UserGuid = userGuid; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UserProfiles/UserGroup.cs b/src/Strata.ContinuousImprovement.Biz/UserProfiles/UserGroup.cs new file mode 100644 index 0000000..c5e17bb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UserProfiles/UserGroup.cs @@ -0,0 +1,15 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.UserProfiles +{ + [Table("UserGroup", Schema = "dbo")] + public class UserGroup + { + [Key] + public Guid UserGroupGuid { get; set; } + public string Name { get; set; } + public string GlobalId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UserProfiles/UserProfile.cs b/src/Strata.ContinuousImprovement.Biz/UserProfiles/UserProfile.cs new file mode 100644 index 0000000..d204ddb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UserProfiles/UserProfile.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.UserProfiles +{ + [Table("UserProfile", Schema = "dbo")] + public class UserProfile + { + [Key] + public Guid UserGuid { get; set; } + public string UserName { get; set; } + public string NameFull { get; set; } + public string NameFirst { get; set; } + public string NameLast { get; set; } + + public string GetDisplayName() + { + if (!string.IsNullOrWhiteSpace(NameFull)) + return NameFull; + var composed = string.Join(", ", new[] { NameLast, NameFirst }.Where(n => !string.IsNullOrWhiteSpace(n))); + if (!string.IsNullOrEmpty(composed)) + return composed; + return UserName ?? string.Empty; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UserService/CIUser.cs b/src/Strata.ContinuousImprovement.Biz/UserService/CIUser.cs new file mode 100644 index 0000000..57a5cdd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UserService/CIUser.cs @@ -0,0 +1,17 @@ +using Strata.Users.Client.Users.Models; + +namespace Strata.ContinuousImprovement.Biz.UserService +{ + public class CIUser : User + { + public CIUser(User user) + { + this.Username = user.Username; + this.UserGuid = user.UserGuid; + this.FirstName = user.FirstName; + this.LastName = user.LastName; + } + + public string FullName => $"{FirstName} {LastName}"; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UserService/IUserService.cs b/src/Strata.ContinuousImprovement.Biz/UserService/IUserService.cs new file mode 100644 index 0000000..d67b8fd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UserService/IUserService.cs @@ -0,0 +1,17 @@ +using Strata.Users.Client.UserGroups.Models; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.UserService +{ + public interface IUserService + { + Task> GetUsersByGlobalId(string globalId, CancellationToken cancellationToken); + Task> GetUserGroups(CancellationToken cancellationToken); + Task> GetAllCIUsers(CancellationToken cancellationToken); + Task GetUser(Guid userGuid, CancellationToken cancellationToken); + Task> GetJazzUsers(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/UserService/UserService.cs b/src/Strata.ContinuousImprovement.Biz/UserService/UserService.cs new file mode 100644 index 0000000..845c9f1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UserService/UserService.cs @@ -0,0 +1,55 @@ +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.Users.Client; +using Strata.Users.Client.UserGroups.Models; +using Strata.Users.Client.Users.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.UserService +{ + public class UserService : IUserService + { + private readonly IUsersServiceClient _usersService; + + public UserService(IUsersServiceClient usersService) + { + _usersService = usersService; + } + + public async Task> GetUsersByGlobalId(string globalId, CancellationToken cancellationToken) + { + var users = await _usersService.GetAllUserGroupUsersAsync(globalId, cancellationToken); + return users.Select(u => new CIUser(u)) + .Where(u => Guid.TryParse(u.UserGuid.ToString(), out var result) && !string.IsNullOrEmpty(u.UserGuid.ToString())) + .OrderBy(u => u.FullName); + } + public async Task> GetUserGroups(CancellationToken cancellationToken) + => (await _usersService.GetAllUserGroupsAsync(cancellationToken)) + .Where(ug => ug.GlobalId.StartsWith("CCI")); + + public async Task GetUser(Guid userGuid, CancellationToken cancellationToken) + => new CIUser(await _usersService.GetUserAsync(userGuid, cancellationToken)); + + public async Task> GetJazzUsers(CancellationToken cancellationToken) + => (await _usersService.GetAllUsersAsync(false, cancellationToken)) + .Select(u => new CIUser(u)) + .OrderBy(u => u.FullName); + + public async Task> GetAllCIUsers(CancellationToken cancellationToken) + { + var users = new List(); + var userGroups = await GetUserGroups(cancellationToken); + foreach (var userGroup in userGroups) + { + users.AddRange(await _usersService.GetAllUserGroupUsersAsync(userGroup.GlobalId, cancellationToken)); + } + return users.DistinctBy(u => u.UserGuid).Select(u => new CIUser(u)) + .Where(u => u.UserGuid.CheckGuidFormat()) + .OrderBy(u => u.FullName); + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UserSetting/CciUserSetting.cs b/src/Strata.ContinuousImprovement.Biz/UserSetting/CciUserSetting.cs new file mode 100644 index 0000000..c0fe3eb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UserSetting/CciUserSetting.cs @@ -0,0 +1,15 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.UserSetting +{ + [Table("UserSetting", Schema = "cci")] + public class CciUserSetting + { + [Key] + public Guid UserGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/UserSetting/DboUserSetting.cs b/src/Strata.ContinuousImprovement.Biz/UserSetting/DboUserSetting.cs new file mode 100644 index 0000000..87b380e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UserSetting/DboUserSetting.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.UserSetting +{ + [Table("UserSetting", Schema = "dbo")] + public class DboUserSetting + { + [Key] + public Guid UserSettingGuid { get; set; } + public Guid UserGuid { get; set; } + public string Name { get; set; } + public string Setting { get; set; } + + public static string CCI_INITIATIVES_FILTERS = nameof(CCI_INITIATIVES_FILTERS); + public static string CCI_INITIATIVES_COLUMNS = nameof(CCI_INITIATIVES_COLUMNS); + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/UserSetting/IUserSettingService.cs b/src/Strata.ContinuousImprovement.Biz/UserSetting/IUserSettingService.cs new file mode 100644 index 0000000..c6777c0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UserSetting/IUserSettingService.cs @@ -0,0 +1,22 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.InitiativeDetailColumns; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.UserSetting +{ + public interface IUserSettingService : IJazzDbService + { + Task SetUserSetting(CciUserSetting userSetting, CancellationToken cancellationToken); + Task GetDboUserSetting(string name, Guid userGuid, CancellationToken cancellationToken); + Task> GetDboUserSettings(string name, Guid userGuid, CancellationToken cancellationToken); + Task SetDboUserSetting(string name, Guid userGuid, object setting, CancellationToken cancellationToken); + Task SetDboUserSetting(string name, Guid userGuid, string setting, CancellationToken cancellationToken); + Task UpdateFilterForDeletedGroupCategory(string deletedName, CancellationToken cancellationToken); + Task DeleteInitiativeColumnForAllUsers(string name, CancellationToken cancellationToken); + Task UpdateInitiativeColumnForAllUsers(string oldName, string newName, CancellationToken cancellationToken); + Task AddInitiativeColumnForAllUsers(InitiativeDetailColumn initiativeDetail, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UserSetting/UserSettingService.cs b/src/Strata.ContinuousImprovement.Biz/UserSetting/UserSettingService.cs new file mode 100644 index 0000000..3783660 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UserSetting/UserSettingService.cs @@ -0,0 +1,212 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.InitiativeDetailColumns; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Initiatives.Filters; +using Strata.ContinuousImprovement.Biz.Utilities; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.UserSetting +{ + public class UserSettingService : Generics.JazzDbService, IUserSettingService + { + private readonly ILogger _logger; + + public UserSettingService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, IClaimsPrincipalAccessor claimsPrincipalAccessor, + ILogger logger) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor) + { + _logger = logger; + } + + public override async Task GetById(Guid id, CancellationToken cancellationToken) + { + try + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext is null) return default; + return await dbContext.UserSettings.FirstOrDefaultAsync(us => us.UserGuid == id, cancellationToken); + } + catch (Exception ex) + { + _logger.LogServiceError(ex); + throw; + } + } + + public async Task SetUserSetting(CciUserSetting userSetting, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext is null) return default; + var userSettingItem = dbContext?.Set() + .SingleOrDefault(us => us.UserGuid == userSetting.UserGuid); + if (userSettingItem == null) dbContext?.Add(userSetting); + else userSettingItem.ConfigurationGuid = userSetting.ConfigurationGuid; + await dbContext?.SaveChangesAsync(cancellationToken); + return userSetting; + } + + public async Task GetDboUserSetting(string name, Guid userGuid, CancellationToken cancellationToken) + { + var newObj = (T)Activator.CreateInstance(typeof(T)); + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) { return newObj; } + var userSettings = await dbContext.DboUserSettings + .Where(u => u.Name == name && u.UserGuid == userGuid).SingleOrDefaultAsync(cancellationToken); + if (userSettings == null) { return newObj; } + return JsonConvert.DeserializeObject(userSettings.Setting); + } + + public async Task> GetDboUserSettings(string name, Guid userGuid, CancellationToken cancellationToken) + { + var newList = Enumerable.Empty(); + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) { return newList; } + var userSettings = await dbContext.DboUserSettings + .Where(u => u.Name == name && u.UserGuid == userGuid).SingleOrDefaultAsync(cancellationToken); + if (userSettings == null) { return newList; } + return JsonConvert.DeserializeObject>(userSettings.Setting); + } + + public async Task SetDboUserSetting(string name, Guid userGuid, object setting, CancellationToken cancellationToken) + => await SetDboUserSetting(name, userGuid, JsonConvert.SerializeObject(setting), cancellationToken); + + public async Task SetDboUserSetting(string name, Guid userGuid, string setting, CancellationToken cancellationToken) + { + try + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) { return; } + var userSettings = await dbContext.DboUserSettings + .Where(u => u.Name == name + && u.UserGuid == userGuid).SingleOrDefaultAsync(cancellationToken); + if (userSettings == null) + { + userSettings = new DboUserSetting + { + Name = name, + UserGuid = userGuid, + Setting = setting + }; + await dbContext.DboUserSettings.AddAsync(userSettings); + } + else + { + userSettings.Setting = setting; + dbContext.DboUserSettings.Update(userSettings); + } + await dbContext.SaveChangesAsync(); + } + catch (Exception ex) + { + _logger.LogServiceInformation("Error saving user settings"); + } + } + + public async Task UpdateInitiativeColumnForAllUsers(string oldName, string newName, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) { return; } + var userSettings = await dbContext.DboUserSettings + .Where(u => u.Name == "CCI_INITIATIVES_COLUMNS").ToListAsync(cancellationToken); + + userSettings.ForEach(userSetting => + { + var settings = JsonConvert.DeserializeObject>(userSetting.Setting).ToList(); + var oldItem = settings.FirstOrDefault(x => x.Heading == oldName); + + if (oldItem != null) + { + oldItem.Heading = newName; + userSetting.Setting = JsonConvert.SerializeObject(settings); + dbContext.DboUserSettings.Update(userSetting); + } + }); + + await dbContext.SaveChangesAsync(); + } + + public async Task DeleteInitiativeColumnForAllUsers(string name, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) { return; } + var userSettings = await dbContext.DboUserSettings + .Where(u => u.Name == "CCI_INITIATIVES_COLUMNS").ToListAsync(cancellationToken); + + userSettings.ForEach(userSetting => + { + var settings = JsonConvert.DeserializeObject>(userSetting.Setting).ToList(); + var oldItem = settings.FirstOrDefault(x => x.Heading == name); + + if (oldItem != null) + { + settings.Remove(oldItem); + settings.Where(x => x.Index > oldItem.Index).ToList().ForEach(x => x.Index = --x.Index); + + userSetting.Setting = JsonConvert.SerializeObject(settings); + dbContext.DboUserSettings.Update(userSetting); + } + }); + + await dbContext.SaveChangesAsync(); + } + + public async Task AddInitiativeColumnForAllUsers(InitiativeDetailColumn initiativeDetail, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) { return; } + var userSettings = await dbContext.DboUserSettings + .Where(u => u.Name == "CCI_INITIATIVES_COLUMNS").ToListAsync(cancellationToken); + + userSettings.ForEach(userSetting => + { + var settings = JsonConvert.DeserializeObject>(userSetting.Setting).ToList(); + var jsonSetting = settings.Find(item => item.Name == initiativeDetail.Name); + if (jsonSetting is not null) + { + jsonSetting.Heading = initiativeDetail.Heading; + jsonSetting.IsLocked = initiativeDetail.IsLocked; + jsonSetting.IsHidden = initiativeDetail.IsHidden; + jsonSetting.InitiativeRollupColumnId = initiativeDetail.InitiativeRollupColumnId; + } + else + { + settings.Add(initiativeDetail); + userSetting.Setting = JsonConvert.SerializeObject(settings); + } + }); + + await dbContext.SaveChangesAsync(); + } + + public async Task UpdateFilterForDeletedGroupCategory(string deletedName, CancellationToken cancellationToken) + { + await using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + if (dbContext == null) { return; } + var userSettings = await dbContext.DboUserSettings + .Where(u => u.Name == DboUserSetting.CCI_INITIATIVES_FILTERS).ToListAsync(cancellationToken); + + userSettings.ForEach(userSetting => + { + InitiativeFilters filter = JsonConvert.DeserializeObject(userSetting.Setting); + if (filter.GroupByCategory.ToString() == deletedName) + { + filter.GroupByCategory = InitiativeGroupByCategory.Workstream; + userSetting.Setting = JsonConvert.SerializeObject(filter); + dbContext.DboUserSettings.Update(userSetting); + } + }); + + await dbContext.SaveChangesAsync(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/Constants.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/Constants.cs new file mode 100644 index 0000000..f0fbc65 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/Constants.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class Constants + { + public const string TREE_DATA_LEVEL_SEPARATOR = " | "; + public const string NOT_SPECIFIED = "Not Specified"; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/CsvUtils.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/CsvUtils.cs new file mode 100644 index 0000000..1dbc2b9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/CsvUtils.cs @@ -0,0 +1,30 @@ +using CsvHelper.Configuration; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class CsvUtils + { + public sealed class DynamicMap : ClassMap + { + public DynamicMap(IEnumerable columnsToIgnore) + { + AutoMap(CultureInfo.InvariantCulture); + var mapType = typeof(T); + foreach (var column in columnsToIgnore) + { + var property = mapType.GetProperty(column); + if (property != null) + { + Map(mapType, property).Ignore(); + } + } + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/DateExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/DateExtensions.cs new file mode 100644 index 0000000..9274c07 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/DateExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class DateExtensions + { + public const string MEASURE_FORMAT_YYYY_MM_DD = "yyyy-MM-dd"; + public const string OPPORTUNITY_FORMAT_MM_DD_YYYY = "MM/dd/yyyy"; + public static IList GetMonthsBetweenDates(this DateTime startDate, DateTime endDate) + { + var months = new List(); + if (endDate < startDate) { return months; } + + var curDate = startDate; + while (curDate <= endDate) + { + months.Add(curDate.Month); + curDate = curDate.AddMonths(1); + } + return months; + } + + public static DateTime GetStartOrEndDateOfYearPeriod(this DateTime date, int diffYear) + { + var diffDirection = Math.Sign(diffYear); + var result = diffDirection > 0 ? + date.AddYears(diffYear).AddDays(-diffDirection) : + date.AddDays(-diffDirection).AddYears(diffYear); + return result; + } + + public static int GetMonthDifference(DateOnly dateFrom, DateOnly dateTo, bool includeFromMonth = false, bool canProduceNegative = false) + { + if (dateFrom > dateTo) + { + return canProduceNegative ? -GetMonthDifference(dateTo, dateFrom, includeFromMonth, canProduceNegative) : 0; + } + + var yearsDiff = dateTo.Year - dateFrom.Year; + var monthsDiff = dateTo.Month - dateFrom.Month; + + var diff = (yearsDiff * 12) + monthsDiff; + if (includeFromMonth) diff += (diff >= 0 ? 1 : -1); + + return diff; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/DeserializeDetailSelections.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/DeserializeDetailSelections.cs new file mode 100644 index 0000000..aed5fb5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/DeserializeDetailSelections.cs @@ -0,0 +1,44 @@ +using Strata.ContinuousImprovement.Biz.ChargeCode.Opportunities; +using Strata.Schema.Client.Dtos; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class DeserializeDetailSelections + { + public static IEnumerable Selections(this string jsonString) + { + try + { + return JsonSerializer.Deserialize(jsonString).Members; + } + catch + { + try + { + return JsonSerializer.Deserialize>(jsonString); + } + catch + { + try + { + return JsonSerializer.Deserialize>(jsonString) + .Select(x => new MemberDto { Name = x.Name }); + } + catch + { + return Enumerable.Empty(); + } + } + } + } + + public static IEnumerable HierarchyPaths(this string jsonString) + { + return JsonSerializer.Deserialize(jsonString).HierarchyPaths; + } + } + +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/EntityQueryableExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/EntityQueryableExtensions.cs new file mode 100644 index 0000000..bc332a4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/EntityQueryableExtensions.cs @@ -0,0 +1,331 @@ +using Hangfire.Annotations; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.Extensions.Logging; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.DbContexts.Snowflake; +using Strata.Schema.Client.Dtos; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class EntityQueryableExtensions + { + + /// + /// + /// Creates a LINQ query based on a raw SQL query. + /// + /// + /// If the database provider supports composing on the supplied SQL, you can compose on top of the raw SQL query using + /// LINQ operators: context.Blogs.FromSqlRaw("SELECT * FROM Blogs").OrderBy(b => b.Name). + /// + /// + /// As with any API that accepts SQL it is important to parameterize any user input to protect against a SQL injection + /// attack. You can include parameter place holders in the SQL query string and then supply parameter values as additional + /// arguments. Any parameter values you supply will automatically be converted to a : + /// + /// context.Blogs.FromRawSql("SELECT * FROM Blogs WHERE Name = {0}", userSuppliedSearchTerm) + /// + /// However, never pass a concatenated or interpolated string ($"") with non-validated user-provided values + /// into this method. Doing so may expose your application to SQL injection attacks. To use the interpolated string syntax, + /// consider using to create parameters. + /// + /// + /// This overload also accepts instances as parameter values. In addition to using positional + /// placeholders as above ({0}), you can also use named placeholders directly in the SQL query string: + /// + /// context.Blogs.FromRawSql("SELECT * FROM Blogs WHERE Name = @searchTerm", new SqlParameter("@searchTerm", userSuppliedSearchTerm)) + /// + /// The type of the elements of . + /// + /// + /// An to use as the base of the raw SQL query (typically a ). + /// + /// + /// The raw SQL query. + /// An representing the raw SQL query. + [StringFormatMethod("sql")] + public static IQueryable FromRawSql( + this DbSet source, + ILogger logger, + [NotParameterized] string sql) where TEntity : class where T : class + { + var results = Enumerable.Empty().AsQueryable(); + var dbContext = source.GetService().Context; + var dbSetName = typeof(TEntity).Name; + try + { + results = source.FromSqlRaw(sql); + } + catch (Exception ex) + { + var dictionary = new Dictionary { + { "query", new Dictionary + { + { "queryName", dbSetName}, + { "sql-query", sql }, + { "parameters", new Dictionary + { { "count", 0 } } + } + } + } + }; + logger.LogQueryError(ex, dbContext, dbSetName, dictionary); + } + logger.LogServiceInformation(dbContext, $"{dbSetName} returned {results.Count()} results."); + return results; + } + + /// + /// + /// Creates a LINQ query based on a raw SQL query. + /// + /// + /// If the database provider supports composing on the supplied SQL, you can compose on top of the raw SQL query using + /// LINQ operators: context.Blogs.FromSqlRaw("SELECT * FROM Blogs").OrderBy(b => b.Name). + /// + /// + /// As with any API that accepts SQL it is important to parameterize any user input to protect against a SQL injection + /// attack. You can include parameter place holders in the SQL query string and then supply parameter values as additional + /// arguments. Any parameter values you supply will automatically be converted to a : + /// + /// context.Blogs.FromRawSql("SELECT * FROM Blogs WHERE Name = {0}", userSuppliedSearchTerm) + /// + /// However, never pass a concatenated or interpolated string ($"") with non-validated user-provided values + /// into this method. Doing so may expose your application to SQL injection attacks. To use the interpolated string syntax, + /// consider using to create parameters. + /// + /// + /// This overload also accepts instances as parameter values. In addition to using positional + /// placeholders as above ({0}), you can also use named placeholders directly in the SQL query string: + /// + /// context.Blogs.FromRawSql("SELECT * FROM Blogs WHERE Name = @searchTerm", new SqlParameter("@searchTerm", userSuppliedSearchTerm)) + /// + /// The type of the elements of . + /// + /// + /// An to use as the base of the raw SQL query (typically a ). + /// + /// + /// The raw SQL query. + /// The values to be assigned to parameters. + /// An representing the raw SQL query. + [StringFormatMethod("sql")] + public static IQueryable FromRawSql( + this DbSet source, + ILogger logger, + [NotParameterized] string sql, + params object[] parameters) where TEntity : class where T : class + { + var dbContext = source.GetService().Context; + var dbSetName = typeof(TEntity).Name; + try + { + return source.FromSqlRaw(sql, parameters.ToArray()); + } + catch (Exception ex) + { + var paramDictionary = parameters + .SelectMany(p => p.ParamDictionary(keyProp: "ParameterName")) + .ToDictionary(p => p.Key, v => v.Value); + paramDictionary.Add("count", parameters.Count()); + var dictionary = new Dictionary { + { "query", new Dictionary + { + { "queryName", dbSetName}, + { "sql-query", sql }, + { "parameters", paramDictionary } + } + } + }; + logger.LogQueryError(ex, dbContext, dbSetName, dictionary); + } + return Enumerable.Empty().AsQueryable(); + } + + [StringFormatMethod("sql")] + public static IQueryable FromRawSql( + this DbSet source, + ILogger logger, + [NotParameterized] string sql, + [NotParameterized] TimeSpan timeout, + params object[] parameters) where TEntity : class where T : class + { + var dbContext = source.GetService().Context; + dbContext.Database.SetCommandTimeout(timeout); + var dbSetName = typeof(TEntity).Name; + try + { + return source.FromSqlRaw(sql, parameters.ToArray()); + } + catch (Exception ex) + { + var paramDictionary = parameters + .SelectMany(p => p.ParamDictionary(keyProp: "ParameterName")) + .ToDictionary(p => p.Key, v => v.Value); + paramDictionary.Add("count", parameters.Count()); + var dictionary = new Dictionary { + { "query", new Dictionary + { + { "queryName", dbSetName}, + { "sql-query", sql }, + { "parameters", paramDictionary } + } + } + }; + logger.LogQueryError(ex, dbContext, dbSetName, dictionary); + } + return Enumerable.Empty().AsQueryable(); + } + + /// + /// + /// Creates a LINQ query based on a raw SQL query. + /// + /// + /// If the database provider supports composing on the supplied SQL, you can compose on top of the raw SQL query using + /// LINQ operators: context.Blogs.FromSqlRaw("SELECT * FROM Blogs").OrderBy(b => b.Name). + /// + /// + /// As with any API that accepts SQL it is important to parameterize any user input to protect against a SQL injection + /// attack. You can include parameter place holders in the SQL query string and then supply parameter values as additional + /// arguments. Any parameter values you supply will automatically be converted to a : + /// + /// context.Blogs.FromRawSql("SELECT * FROM Blogs WHERE Name = {0}", userSuppliedSearchTerm) + /// + /// However, never pass a concatenated or interpolated string ($"") with non-validated user-provided values + /// into this method. Doing so may expose your application to SQL injection attacks. To use the interpolated string syntax, + /// consider using to create parameters. + /// + /// + /// This overload also accepts instances as parameter values. In addition to using positional + /// placeholders as above ({0}), you can also use named placeholders directly in the SQL query string: + /// + /// context.Blogs.FromRawSql("SELECT * FROM Blogs WHERE Name = @searchTerm", new SqlParameter("@searchTerm", userSuppliedSearchTerm)) + /// + /// The type of the elements of . + /// + /// + /// An to use as the base of the raw SQL query (typically a ). + /// + /// + /// The raw SQL query. + /// The values to be assigned to parameters. + /// An representing the raw SQL query. + [StringFormatMethod("sql")] + public static IQueryable FromRawSql( + this DbSet source, + ILogger logger, + [NotParameterized] string sql, + IEnumerable parameters) where TEntity : class where T : class + { + var dbContext = source.GetService().Context; + var dbSetName = typeof(TEntity).Name; + try + { + return source.FromSqlRaw(sql, parameters); + } + catch (Exception ex) + { + var paramDictionary = parameters + .SelectMany(p => p.ParamDictionary(keyProp: "ParameterName")) + .ToDictionary(p => p.Key, v => v.Value); + paramDictionary.Add("count", parameters.Count()); + var dictionary = new Dictionary { + { "query", new Dictionary + { + { "queryName", dbSetName}, + { "sql-query", sql }, + { "parameters", paramDictionary } + } + } + }; + logger.LogQueryError(ex, dbContext, dbSetName, dictionary); + } + return Enumerable.Empty().AsQueryable(); + } + + #region Charge Code Opportunity Query Extensions + + public static async Task> PerformQuery(this string query, IEnumerable sqlParameters, + ISnowflakeDatabaseContext snowflakeDbContext, IJazzDbContext jazzDbContext, ILogger logging, + CancellationToken cancellationToken) + where TEntity : class + where TService : class + { + var result = new List(); + + try + { + if (snowflakeDbContext != null) + { + var snowflakeQuery = query.ConvertSqlToSnowflakeParameterSyntax(); + var snowflakeParams = sqlParameters.Select(p => new KeyValuePair(p.ParameterName.Replace("@", ":"), p.Value)).ToDictionary(); + result = (await snowflakeDbContext.QueryAsync(snowflakeQuery, typeof(TService).Name.SplitCamelCase(), snowflakeParams)).ToList(); + } + else + { + query = query.InterpolateQueryArrayValues(ref sqlParameters); + var dbSet = ((JazzDbContext)jazzDbContext).Set(); + if (dbSet == null) { return result; } + result = await dbSet.FromRawSql(logging, query, TimeSpan.FromMinutes(30), sqlParameters.ToArray()).ToListAsync(cancellationToken); + } + } + catch (Exception ex) + { + logging.LogServiceError(ex, info: new Dictionary + { + {nameof(query), query }, + { nameof(sqlParameters), sqlParameters.ToDictionary(k => k.ParameterName, v => v.Value)} + }); + } + return result; + } + + public static string InterpolateQueryArrayValues(this string query, ref IEnumerable sqlParameters) + { + var sqlParametersToKeep = sqlParameters + .Where(p => query.Contains(p.ParameterName, StringComparison.CurrentCultureIgnoreCase)) + .Select(p => p.ParameterName).ToList(); + foreach (var sqlParam in sqlParameters.Where(p => p.Value is IEnumerable)) + { + var regex = new Regex($@"(\s+IN)\s+\(\s*{sqlParam.ParameterName}\s*\)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + if (regex.IsMatch(query)) + { + var csv = sqlParam.Value.GetType().GetGenericArguments()[0].Name switch + { + nameof(Decimal) => string.Join(",", (IEnumerable)sqlParam.Value), + nameof(Double) => string.Join(",", (IEnumerable)sqlParam.Value), + nameof(Byte) => string.Join(",", (IEnumerable)sqlParam.Value), + nameof(Int16) => string.Join(",", (IEnumerable)sqlParam.Value), + nameof(Int32) => string.Join(",", (IEnumerable)sqlParam.Value), + nameof(Int64) => string.Join(",", (IEnumerable)sqlParam.Value), + nameof(String) => "'" + string.Join("','", (IEnumerable)sqlParam.Value) + "'", + _ => "'" + string.Join("','", ((IEnumerable)sqlParam.Value).Cast().ToArray()) + "'" + }; + query = regex.Replace(query, $"$1 ({csv})"); + sqlParametersToKeep = sqlParametersToKeep.Where(p => p != sqlParam.ParameterName).ToList(); + } + } + sqlParameters = sqlParameters.Where(p => sqlParametersToKeep.Contains(p.ParameterName)); + return query; + } + + #endregion + + public static IEnumerable GetIdsForMemberDtos(this IEnumerable members) + => members.Select(x => int.TryParse(x.Id, out var id) ? id : 0).Where(id => id >= 0); + public static string GetCsvForMemberDtos(this IEnumerable members) + => string.Join(",", members.Select(x => int.TryParse(x.Id, out var id) ? id : 0).Where(id => id >= 0)); + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/FTPClientExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/FTPClientExtensions.cs new file mode 100644 index 0000000..0aade93 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/FTPClientExtensions.cs @@ -0,0 +1,42 @@ +using FluentFTP; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Xml.Serialization; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class FTPClientExtensions + { + /// + /// Deserializes archived (.zip) XML files from FTP client + /// + /// + /// + /// + /// + /// + public static T DeserializeZippedXMLFromFTP(FtpClient client, string archiveFileNameStartsWith, string xmlRootElement) where T : class + { + var files = client.GetListing("/"); + var targetFile = files.Where(file => file.Name.StartsWith(archiveFileNameStartsWith)).OrderByDescending(file => file.Modified).FirstOrDefault(); + if (targetFile == null) { return null; } + + T readFile = null; + var zipFileStream = new MemoryStream(); + if (client.DownloadStream(zipFileStream, targetFile.FullName)) + { + zipFileStream.Position = 0; + using var zip = new ZipArchive(zipFileStream, ZipArchiveMode.Read); + var file = zip.Entries.FirstOrDefault(entry => entry.Name.StartsWith(archiveFileNameStartsWith)); + if (file == null) { return null; } + + using var stream = file.Open(); + var serializer = new XmlSerializer(typeof(T), new XmlRootAttribute(xmlRootElement)); + readFile = (T)serializer.Deserialize(stream); + } + + return readFile; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlag.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlag.cs new file mode 100644 index 0000000..4d80d5f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlag.cs @@ -0,0 +1,77 @@ +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + /// + /// Enumeration of all feature flags available in the CCI (Continuous Improvement) module. + /// Each enumeration value corresponds to a specific feature flag that can be toggled on or off + /// to control system behavior and feature availability. + /// + /// + /// + public enum FeatureFlag + { + /// + /// Controls access to charge code beta features. + /// + ChargeCodeBeta, + + /// + /// Controls whether continuous integration benchmarking is enabled. + /// + CiBenchmarkingEnabled, + + /// + /// Controls if the Add Opportunity button on the Exploration Population page launches the Strategic Opportunity Wizard. + /// + CiExplorationPopulationStrategicOpportunityWizard, + + /// + /// Controls access to flexible exploration features for non-SDT employees. + /// + CiFlexibleExplorationEnabled, + + /// + /// Controls access to the flexible exploration information page (third page). + /// + CiFlexibleExplorationPage3Enabled, + + /// + /// Controls whether CCI opportunity workbooks functionality is enabled in the web UI. + /// + CiOpportunityWorkbooks, + + /// + /// Controls whether encounter opportunities are enabled in the Tempo interface. + /// + EnableEncounterOpportunityInTempo, + + /// + /// Controls whether payroll opportunities are enabled in the Tempo interface. + /// + EnablePayrollOpportunityInTempo, + + /// + /// Controls whether Snowflake-based decision support queries are executed instead of traditional SQL Server queries. + /// + IsDecisionSupportQueriesEnabled, + + /// + /// Controls whether exploration population data is loaded from PostgreSQL instead of SQL Server. + /// + LoadExplorationPopulationDataFromPostgres, + + /// + /// Enables the evolution of flexible exploration to use the strategic opportunity process. + /// + StrategicOpportunityEnabled, + + /// + /// Enables the performance logging for flexible exploration features. + /// + CiFlexExPerformanceLoggingEnabled, + + /// + /// Controls whether the General Ledger opportunity type is available in the Strategic Opportunity wizard. + /// + CiStrategicOpportunityGLEnabled + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlagExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlagExtensions.cs new file mode 100644 index 0000000..a154b86 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlagExtensions.cs @@ -0,0 +1,23 @@ +using Strata.FeatureFlags.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class FeatureFlagExtensions + { + /// + /// Gets the all of feature flags and their states for the given database and area + /// + /// The IFeatureFlagServiceClient instance + /// The id of the database to check + /// The area abbreviation to filter the results by + /// A to observe while waiting for the task to complete. + public static async Task> GetFeatureFlagClientStatesByArea(this IFeatureFlagServiceClient client, Guid databaseGuid, string areaAbbrevation, CancellationToken cancellationToken = default) + => (await client.GetFeatureFlagStatesByClient(databaseGuid, cancellationToken)) + .Where(ff => ff.Area.Equals(areaAbbrevation, StringComparison.OrdinalIgnoreCase)); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlagLookup.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlagLookup.cs new file mode 100644 index 0000000..14ae472 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlagLookup.cs @@ -0,0 +1,134 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + /// + /// Provides a centralized lookup mechanism for mapping feature flag enumeration values to their corresponding string keys. + /// This static class serves as the single source of truth for all feature flag key mappings used throughout the CCI module. + /// + /// + /// + public static class FeatureFlagLookup + { + public static readonly IImmutableDictionary Values = new Dictionary + { + /// + /// Maps the charge code beta feature flag to its string key. + /// Key: "chargecodebeta" + /// Purpose: Controls access to charge code beta features + /// Default: false (hide beta features) + /// Owner: CCI Team + /// + { FeatureFlag.ChargeCodeBeta, "chargecodebeta" }, + + /// + /// Maps the continuous improvement benchmarking feature flag to its string key. + /// Key: "cibenchmarkingenabled" + /// Purpose: Controls benchmarking features in continuous improvement + /// Default: false + /// Owner: CCI Team + /// + { FeatureFlag.CiBenchmarkingEnabled, "cibenchmarkingenabled" }, + + /// + /// Maps the CI exploration population strategic opportunity wizard feature flag to its string key. + /// Key: "ciexplorationpopulationstrategicopportunitywizard" + /// Purpose: Controls if the Add Opportunity button on the Exploration Population page launches the Strategic Opportunity Wizard + /// Default: false (use standard workflow) + /// Owner: CCI Team + /// + { FeatureFlag.CiExplorationPopulationStrategicOpportunityWizard, "ciexplorationpopulationstrategicopportunitywizard" }, + + /// + /// Maps the CI flexible exploration feature flag to its string key. + /// Key: "ciflexibleexplorationenabled" + /// Purpose: Controls access to flexible exploration features for non-SDT employees + /// Default: false (SDT employees always have access) + /// Owner: CCI Team + /// + { FeatureFlag.CiFlexibleExplorationEnabled, "ciflexibleexplorationenabled" }, + + /// + /// Maps the flexible exploration page 3 feature flag to its string key. + /// Key: "ciflexibleexplorationpage3enabled" + /// Purpose: Controls access to the flexible exploration information page (third page) + /// Default: false + /// Owner: CCI Team + /// + { FeatureFlag.CiFlexibleExplorationPage3Enabled, "ciflexibleexplorationpage3enabled" }, + + /// + /// Maps the CCI opportunity workbooks feature flag to its string key. + /// Key: "ciopportunityworkbooks" + /// Purpose: Controls availability of workbooks functionality in web UI + /// Default: false (workbooks functionality hidden) + /// Owner: CCI Team + /// + { FeatureFlag.CiOpportunityWorkbooks, "ciopportunityworkbooks" }, + + /// + /// Maps the encounter opportunity tempo feature flag to its string key. + /// Key: "enableencounteropportunityintempo" + /// Purpose: Controls whether encounter opportunities are enabled in the Tempo interface + /// Default: false (use Jazz interface) + /// Owner: CCI Team + /// + { FeatureFlag.EnableEncounterOpportunityInTempo, "enableencounteropportunityintempo" }, + + /// + /// Maps the payroll opportunity tempo feature flag to its string key. + /// Key: "enablepayrollopportunityintempo" + /// Purpose: Controls whether payroll opportunities are enabled in the Tempo interface + /// Default: false (use Jazz interface) + /// Owner: CCI Team + /// + { FeatureFlag.EnablePayrollOpportunityInTempo, "enablepayrollopportunityintempo" }, + + /// + /// Maps the decision support queries feature flag to its string key. + /// Key: "is-decision-support-queries-enabled" + /// Purpose: Controls Snowflake vs SQL Server query execution strategy + /// Default: false (use SQL Server queries) + /// Owner: CCI Team + /// + { FeatureFlag.IsDecisionSupportQueriesEnabled, "is-decision-support-queries-enabled" }, + + /// + /// Maps the exploration population data source feature flag to its string key. + /// Key: "loadexplorationpopulationdatafrompostgres" + /// Purpose: Controls PostgreSQL vs SQL Server data source for exploration population data + /// Default: false (use SQL Server data source) + /// Owner: CCI Team + /// + { FeatureFlag.LoadExplorationPopulationDataFromPostgres, "loadexplorationpopulationdatafrompostgres" }, + + /// + /// Maps the strategic opportunity evolution feature flag to its string key. + /// Key: "strategic-opportunity-enabled" + /// Purpose: Enables evolution of flexible exploration to use strategic opportunity process + /// Default: false (use traditional exploration process) + /// Owner: CCI Team + /// + { FeatureFlag.StrategicOpportunityEnabled, "strategic-opportunity-enabled" }, + + /// + /// Maps the flexible exploration performance logging feature flag to its string key. + /// Key: "ciflexexperformanceloggingenabled" + /// Purpose: Enables performance logging for flexible exploration features + /// Default: false (performance logging disabled) + /// Owner: CCI Team + /// + { FeatureFlag.CiFlexExPerformanceLoggingEnabled, "ciflexexperformanceloggingenabled" }, + + /// + /// Maps the strategic opportunity GL enabled feature flag to its string key. + /// Key: "cistrategicopportunitygl" + /// Purpose: Controls whether the General Ledger opportunity type is available in the Strategic Opportunity wizard + /// Default: false (GL type hidden) + /// Owner: CCI Team + /// + { FeatureFlag.CiStrategicOpportunityGLEnabled, "cistrategicopportunitygl" } + }.ToImmutableDictionary(); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlagWrapper.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlagWrapper.cs new file mode 100644 index 0000000..53dea4e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/FeatureFlagWrapper.cs @@ -0,0 +1,107 @@ +using Strata.CoreLib.Claims; +using Strata.CoreLib.Claims.Extensions; +using Strata.FeatureFlags.Client; +using System; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public class FeatureFlagWrapper( + IFeatureFlagServiceClient featureFlagServiceClient, + IClaimsPrincipalAccessor claimsPrincipalAccessor + ) : IFeatureFlagWrapper + { + private readonly ClaimsPrincipal _user = claimsPrincipalAccessor.GetCurrentClaimsPrincipal(); + + /// + /// Evaluates whether a specific feature flag is enabled. + /// + /// The feature flag enumeration value to evaluate. + /// The default value to return if the feature flag evaluation fails or is not configured. Default is false. + /// + /// true if the feature flag is enabled; false if disabled or if evaluation fails. + /// Returns the if the feature flag cannot be evaluated. + /// + /// Thrown when the is not found in the lookup dictionary. + public async Task IsFeatureFlagOn(FeatureFlag featureFlag, Guid clientId, bool defaultValue = false) + { + string flagKey = FeatureFlagLookup.Values[featureFlag]; + return await featureFlagServiceClient.IsEnabledAsync(flagKey, clientId, defaultValue); + } + + public async Task IsChargeCodeBetaEnabled(bool defaultValue = false) + => await IsChargeCodeBetaEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsChargeCodeBetaEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.ChargeCodeBeta, clientId, defaultValue); + + public async Task IsCiBenchmarkingEnabled(bool defaultValue = false) + => await IsCiBenchmarkingEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsCiBenchmarkingEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.CiBenchmarkingEnabled, clientId, defaultValue); + + public async Task IsCiExplorationPopulationStrategicOpportunityWizardEnabled(bool defaultValue = false) + => await IsCiExplorationPopulationStrategicOpportunityWizardEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsCiExplorationPopulationStrategicOpportunityWizardEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.CiExplorationPopulationStrategicOpportunityWizard, clientId, defaultValue); + + public async Task IsCiFlexibleExplorationEnabled(bool defaultValue = false) + => await IsCiFlexibleExplorationEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsCiFlexibleExplorationEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.CiFlexibleExplorationEnabled, clientId, defaultValue); + + public async Task IsCiFlexibleExplorationPage3Enabled(bool defaultValue = false) + => await IsCiFlexibleExplorationPage3Enabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsCiFlexibleExplorationPage3Enabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.CiFlexibleExplorationPage3Enabled, clientId, defaultValue); + + public async Task IsCiOpportunityWorkbooksEnabled(bool defaultValue = false) + => await IsCiOpportunityWorkbooksEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsCiOpportunityWorkbooksEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.CiOpportunityWorkbooks, clientId, defaultValue); + + public async Task IsDecisionSupportQueriesEnabled(bool defaultValue = false) + => await IsDecisionSupportQueriesEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsDecisionSupportQueriesEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.IsDecisionSupportQueriesEnabled, clientId, defaultValue); + + public async Task IsEnableEncounterOpportunityInTempoEnabled(bool defaultValue = false) + => await IsEnableEncounterOpportunityInTempoEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsEnableEncounterOpportunityInTempoEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.EnableEncounterOpportunityInTempo, clientId, defaultValue); + + public async Task IsEnablePayrollOpportunityInTempoEnabled(bool defaultValue = false) + => await IsEnablePayrollOpportunityInTempoEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsEnablePayrollOpportunityInTempoEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.EnablePayrollOpportunityInTempo, clientId, defaultValue); + + public async Task IsLoadExplorationPopulationDataFromPostgresEnabled(bool defaultValue = false) + => await IsLoadExplorationPopulationDataFromPostgresEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsLoadExplorationPopulationDataFromPostgresEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.LoadExplorationPopulationDataFromPostgres, clientId, defaultValue); + + public async Task IsStrategicOpportunityEnabled(bool defaultValue = false) + => await IsStrategicOpportunityEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsStrategicOpportunityEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.StrategicOpportunityEnabled, clientId, defaultValue); + + public async Task IsCiFlexExPerformanceLoggingEnabled(bool defaultValue = false) + => await IsCiFlexExPerformanceLoggingEnabled(_user.GetDatabaseGuid(), defaultValue); + + public async Task IsCiFlexExPerformanceLoggingEnabled(Guid clientId, bool defaultValue = false) + => await IsFeatureFlagOn(FeatureFlag.CiFlexExPerformanceLoggingEnabled, clientId, defaultValue); + + } + +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/HangfireExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/HangfireExtensions.cs new file mode 100644 index 0000000..75a904d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/HangfireExtensions.cs @@ -0,0 +1,27 @@ +using Hangfire.States; +using Strata.Hangfire.Jazz.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class HangfireExtensions + { + public static async Task WaitForJob(this IJazzHangfireServiceClient client, Guid jobId, CancellationToken cancellationToken) + { + var result = await client.GetJobInfoAsync(jobId, cancellationToken); + var notDoneJobStates = new string[] { ProcessingState.StateName, ScheduledState.StateName, EnqueuedState.StateName }; + while (notDoneJobStates.Contains(result.State.Name)) + { + await Task.Delay(500); + result = await client.GetJobInfoAsync(jobId, cancellationToken); + } + if (result.State.Name != SucceededState.StateName) { return false; } + return true; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/IFeatureFlagWrapper.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/IFeatureFlagWrapper.cs new file mode 100644 index 0000000..5a5bd29 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/IFeatureFlagWrapper.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public interface IFeatureFlagWrapper + { + Task IsChargeCodeBetaEnabled(bool defaultValue = false); + Task IsChargeCodeBetaEnabled(Guid clientId, bool defaultValue = false); + Task IsCiBenchmarkingEnabled(bool defaultValue = false); + Task IsCiBenchmarkingEnabled(Guid clientId, bool defaultValue = false); + Task IsCiExplorationPopulationStrategicOpportunityWizardEnabled(bool defaultValue = false); + Task IsCiExplorationPopulationStrategicOpportunityWizardEnabled(Guid clientId, bool defaultValue = false); + Task IsCiFlexibleExplorationEnabled(bool defaultValue = false); + Task IsCiFlexibleExplorationEnabled(Guid clientId, bool defaultValue = false); + Task IsCiFlexibleExplorationPage3Enabled(bool defaultValue = false); + Task IsCiFlexibleExplorationPage3Enabled(Guid clientId, bool defaultValue = false); + Task IsCiOpportunityWorkbooksEnabled(bool defaultValue = false); + Task IsCiOpportunityWorkbooksEnabled(Guid clientId, bool defaultValue = false); + Task IsDecisionSupportQueriesEnabled(bool defaultValue = false); + Task IsDecisionSupportQueriesEnabled(Guid clientId, bool defaultValue = false); + Task IsEnableEncounterOpportunityInTempoEnabled(bool defaultValue = false); + Task IsEnableEncounterOpportunityInTempoEnabled(Guid clientId, bool defaultValue = false); + Task IsEnablePayrollOpportunityInTempoEnabled(bool defaultValue = false); + Task IsEnablePayrollOpportunityInTempoEnabled(Guid clientId, bool defaultValue = false); + Task IsFeatureFlagOn(FeatureFlag featureFlag, Guid clientId, bool defaultValue = false); + Task IsLoadExplorationPopulationDataFromPostgresEnabled(bool defaultValue = false); + Task IsLoadExplorationPopulationDataFromPostgresEnabled(Guid clientId, bool defaultValue = false); + Task IsStrategicOpportunityEnabled(bool defaultValue = false); + Task IsStrategicOpportunityEnabled(Guid clientId, bool defaultValue = false); + Task IsCiFlexExPerformanceLoggingEnabled(bool defaultValue = false); + Task IsCiFlexExPerformanceLoggingEnabled(Guid clientId, bool defaultValue = false); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/JobInfoDtoExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/JobInfoDtoExtensions.cs new file mode 100644 index 0000000..b288942 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/JobInfoDtoExtensions.cs @@ -0,0 +1,17 @@ +using Strata.Hangfire.Jazz.Client.Models; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class JobInfoDtoExtensions + { + public static Status ToStatus(this JobInfoDto jobInfo) + => jobInfo.State.Name.Map(); + public static bool IsSuccessful(this JobInfoDto jobInfo) + => jobInfo.ToStatus().IsSuccessful(); + + public static bool IsFinalStatus(this JobInfoDto jobInfo) + => jobInfo.ToStatus().IsFinalStatus(); + public static bool IsRunningStatus(this JobInfoDto jobInfo) + => jobInfo.ToStatus().IsRunningStatus(); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/ListExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/ListExtensions.cs new file mode 100644 index 0000000..f9db341 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/ListExtensions.cs @@ -0,0 +1,105 @@ +using Hangfire.Annotations; +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + [ExcludeFromCodeCoverage] + public static class ListExtensions + { + public static Dictionary ColumnDictionary(this IEnumerable data, bool splitCamelCase = false) + { + return data.FirstOrDefault()?.GetType().GetProperties() + .Select((p, i) => new { i, p.Name }).ToDictionary(key => splitCamelCase ? key.Name.SplitCamelCase() : key.Name, value => value.i + 1) + ?? new Dictionary(); + } + + public static Dictionary ParamDictionary(this IEnumerable data, bool splitCamelCase = false, string keyProp = null) + { + return data + .SelectMany(p => p.ParamDictionary(splitCamelCase, keyProp)) + .ToDictionary(p => p.Key, v => v.Value); + } + + public static Dictionary ParamDictionary(this T data, bool splitCamelCase = false, string keyProp = null) + { + var props = data.GetType().GetProperties().Select((p, i) => new { i, p.Name }); + if (!props.Any()) return new Dictionary(); + var key = string.IsNullOrEmpty(keyProp) ? "" : data.GetType().GetProperty(keyProp).GetValue(data, null).ToString(); + var value = props + .OrderBy(p => p.Name) + .ToDictionary( + key => splitCamelCase ? key.Name.SplitCamelCase() : key.Name, + value => data.GetType().GetProperty(value.Name).GetValue(data, null) + ); + + return new Dictionary { { key, value } }; + } + + public static Dictionary ColumnDictionary(this DataTable data, bool splitCamelCase = false) + { + var result = new Dictionary(); + var idx = 0; + foreach (DataColumn column in data.Columns) + { + result.Add(splitCamelCase ? column.ColumnName.SplitCamelCase() : column.ColumnName, ++idx); + } + return result; + } + + public static DataTable AsDataTable(this IEnumerable items) + { + var data = items.ToArray(); + if (data.Count() == 0) return null; + + var dt = new DataTable(); + foreach (var key in ((IDictionary)data[0]).Keys) + { + dt.Columns.Add(key); + } + foreach (var d in data) + { + dt.Rows.Add(((IDictionary)d).Values.ToArray()); + } + return dt; + } + + public static bool IsEmpty(this IEnumerable items) + => items == null || !items.Any(); + + public static double Median(this IEnumerable source, Func predicate) + { + ArgumentNullException.ThrowIfNull(predicate); + + if (source.IsEmpty()) return 0; + return source.Select(x => predicate(x)).Median(); + } + + public static String ToCSVList(this IEnumerable items, Func selector) + { + return ToSeperatedList(items, ",", selector); + } + + public static String ToSeperatedList([InstantHandle] this IEnumerable items, String delimiter, Func selector) + { + var sb = new StringBuilder(); + foreach (T s in items) + { + sb.AppendFormat("{0}{1}", selector(s), delimiter); + } + + var result = sb.ToString().TrimEnd(delimiter.ToCharArray()); + return result; + } + + public static IEnumerable ExcludeNullAndEmpty(this IEnumerable items) + { + return items.Where(x => !string.IsNullOrWhiteSpace(x)); + } + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/LogUtilsExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/LogUtilsExtensions.cs new file mode 100644 index 0000000..23ce994 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/LogUtilsExtensions.cs @@ -0,0 +1,369 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.GMDN; +using Strata.Id.Client.DTO; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.IO; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + [ExcludeFromCodeCoverage] + public static class LogUtilsExtensions + { + internal static readonly string Microservice = "strata-continuousimprovement-"; + + public static Dictionary ToDictionary(this object obj) + { + var json = JsonConvert.SerializeObject(obj); + Dictionary dictionary = new Dictionary(); + try + { + dictionary = JsonConvert.DeserializeObject>(json); + } + catch + { + dictionary = new Dictionary { { "object", (TValue)obj } }; + } + return dictionary; + } + + public static Dictionary DatabaseDictionary(this StrataIdFullDto database, string service) + { + var dictionary = new Dictionary { { "service", $"{Microservice}{service}" } }; + if (database != null) + { + dictionary.Add("db", new Dictionary { + { "guid", database.DatabaseGUID }, + { "instance", database.PhysicalName }, + { "server", database.ServerName }, + { "strata_id", database.StrataId }, + { "org_pin", database.OrgPin }, + { "name", database.DatabaseName } + }); + } + return dictionary; + } + + /// + /// + /// This turns the dbContext connction string into a dictionary of elements. + /// + /// + /// It splits the string by semi-colons, then splits each of those by the equal symbol. + /// + /// + /// The left side is the dictionary key, the right side is the dictionary value, ignoring the Password (if present). + /// + /// + /// + /// + public static Dictionary 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 => !new[] { "password" }.Contains(x.key.ToLowerInvariant())) + .ToDictionary(x => x.key, x => x.value) ?? new Dictionary(); + return new Dictionary { { "service", $"{Microservice}{service}" }, { "dbContext", dictionary } }; + } + + public static void LogContextError(this ILogger logger, Exception exception, DbContext dbContext, string message, Dictionary info = null) + => logger.LogQueryError(exception, dbContext, message, info); + + public static void LogContextError(this ILogger logger, Exception exception, StrataIdFullDto dbContext, string message, Dictionary info = null) + => logger.LogQueryError(exception, dbContext, message, info); + + public static void LogQueryError(this ILogger logger, Exception exception, DbContext dbContext, string message, Dictionary info = null) + { + if (logger == null) { return; } + var dictionary = dbContext.Database.DbContextDictionary(typeof(T).Name); + if (info != null) + { + dictionary.Add("information", info); + } + using (logger.BeginScope(dictionary)) + logger.LogError(exception, message); + } + + public static void LogQueryError(this ILogger logger, Exception exception, StrataIdFullDto dbContext, string message, Dictionary info = null) + { + if (logger == null) { return; } + var dictionary = dbContext.DatabaseDictionary(typeof(T).Name); + if (info != null) + { + dictionary.Add("information", info); + } + using (logger.BeginScope(dictionary)) + logger.LogError(exception, message); + } + + public static void LogContextWarning(this ILogger logger, DbContext dbContext, string message, Dictionary info = null) + { + if (logger == null) { return; } + var dictionary = dbContext.Database.DbContextDictionary(typeof(T).Name); + if (info != null) + { + dictionary.Add("information", info); + } + using (logger.BeginScope(dictionary)) + logger.LogWarning(message); + } + + public static void LogQueryInformation(this ILogger logger, DbContext dbContext, + string queryName, + [NotParameterized] string sql, + params object[] parameters) + { + Dictionary paramDictionary = default; + try + { + paramDictionary = parameters.ParamDictionary(); + } + catch + { + // ILB + } + var dictionary = dbContext.Database.DbContextDictionary(typeof(T).Name); + dictionary.Add("query", new Dictionary { + { "name", queryName}, + { "sql", sql}, + { "parameters", paramDictionary} + }); + using (logger.BeginScope(dictionary)) + logger.LogInformation(queryName); + } + + public static void LogQueryError(this ILogger logger, Exception exception, JazzDbContext dbContext, string queryName, string sql) + { + if (logger == null) { return; } + var dictionary = dbContext.Database.DbContextDictionary(typeof(T).Name); + dictionary.Add("query", queryName); + using (logger.BeginScope(dictionary)) + logger.LogError(exception.InnerException ?? exception, sql); + } + + public static void LogServiceError(this ILogger logger, Exception exception, string message, IEnumerable terms) + => logger.LogServiceError(exception, message, new Dictionary { { "terms", terms } }); + + public static void LogServiceError(this ILogger logger, Exception exception, string message = null, Dictionary info = null) + { + if (logger == null) { return; } + if (exception.InnerException != null) + { + logger.LogServiceError(exception.InnerException, message, info); + return; + } + var dictionary = new Dictionary { + { "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); + } + + public static void LogServiceInformation(this ILogger logger, string message) + { + if (logger == null) { return; } + var dictionary = new Dictionary { + { "service", Microservice + typeof(T).Name } + }; + using (logger.BeginScope(dictionary)) + logger.LogInformation(message); + } + + public static void LogServiceInformation(this ILogger logger, DbContext dbContext, string message, Dictionary info = null) + { + if (logger == null) { return; } + var dictionary = dbContext.Database.DbContextDictionary(typeof(T).Name); + if (info != null) + { + dictionary.Add("information", info); + } + using (logger.BeginScope(dictionary)) + logger.LogInformation(message); + } + + public static void LogServiceInformation(this ILogger logger, string message, Dictionary dictionary) + { + if (logger == null) { return; } + dictionary.Add("service", Microservice + typeof(T).Name); + using (logger.BeginScope(dictionary)) + logger.LogInformation(message); + } + + public static void LogServiceQuery(this ILogger logger, string message, string query, IEnumerable queryFilters, object queryParams) + { + var dictionary = new Dictionary + { + {"service", Microservice + typeof(T).Name}, + {"query", new Dictionary + { + { nameof(query), query }, + { nameof(queryParams), queryParams } + } + } + }; + using (logger.BeginScope(dictionary)) + logger.LogWarning(message); + } + + private static string SanitizeForLogging(string value) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + // Remove line breaks to mitigate log forging via user-controlled values. + return value.Replace("\r", string.Empty) + .Replace("\n", string.Empty); + } + + public static void LogOpportunityError(this ILogger logger, Exception exception, StrataIdFullDto dbContext, string name, Guid opportunityGuid) + { + if (exception.InnerException != null) + { + logger.LogOpportunityError(exception.InnerException, dbContext, SanitizeForLogging(name), opportunityGuid); + return; + } + var dictionary = dbContext.DatabaseDictionary(typeof(T).Name); + dictionary.Add("opportunity", SanitizeForLogging(name)); + dictionary.Add("opportunityGuid", opportunityGuid); + using (logger.BeginScope(dictionary)) + logger.LogError(exception.InnerException ?? exception, exception.InnerException?.Message ?? exception.Message); + } + + public static void LogOpportunityError(this ILogger logger, Exception exception, JazzDbContext dbContext, string name, Guid opportunityGuid) + { + if (exception.InnerException != null) + { + logger.LogOpportunityError(exception.InnerException, dbContext, SanitizeForLogging(name), opportunityGuid); + return; + } + var dictionary = dbContext.Database.DbContextDictionary(typeof(T).Name); + dictionary.Add("opportunity", SanitizeForLogging(name)); + dictionary.Add("opportunityGuid", opportunityGuid); + using (logger.BeginScope(dictionary)) + logger.LogError(exception.InnerException ?? exception, exception.InnerException?.Message ?? exception.Message); + } + + public static void LogOpportunityError(this ILogger logger, Exception exception, string name, Guid opportunityGuid) + { + if (exception.InnerException != null) + { + logger.LogOpportunityError(exception.InnerException, SanitizeForLogging(name), opportunityGuid); + return; + } + var dictionary = new Dictionary { + { "service", $"strata-continuousimprovement-{typeof(T).Name}" }, + { "opportunity", SanitizeForLogging(name)}, + { "opportunityGuid", opportunityGuid } + }; + using (logger.BeginScope(dictionary)) + logger.LogError(exception.InnerException ?? exception, exception.InnerException?.Message ?? exception.Message); + } + + public static void LogInitiativeError(this ILogger logger, Exception exception, StrataIdFullDto dbContext, string name, Guid opportunityGuid, Guid initiativeGuid) + { + if (exception.InnerException != null) + { + logger.LogInitiativeError(exception.InnerException, dbContext, SanitizeForLogging(name), opportunityGuid, initiativeGuid); + return; + } + var dictionary = dbContext.DatabaseDictionary(typeof(T).Name); + dictionary.Add("initiative", SanitizeForLogging(name)); + dictionary.Add("opportunityGuid", opportunityGuid); + dictionary.Add("initiativeGuid", initiativeGuid); + using (logger.BeginScope(dictionary)) + logger.LogError(exception.InnerException ?? exception, exception.InnerException?.Message ?? exception.Message); + } + + public static void LogInitiativeError(this ILogger logger, Exception exception, JazzDbContext dbContext, string name, Guid opportunityGuid, Guid initiativeGuid) + { + if (exception.InnerException != null) + { + logger.LogInitiativeError(exception.InnerException, dbContext, SanitizeForLogging(name), opportunityGuid, initiativeGuid); + return; + } + var dictionary = dbContext.Database.DbContextDictionary(typeof(T).Name); + dictionary.Add("initiative", SanitizeForLogging(name)); + dictionary.Add("opportunityGuid", opportunityGuid); + dictionary.Add("initiativeGuid", initiativeGuid); + using (logger.BeginScope(dictionary)) + logger.LogError(exception.InnerException ?? exception, exception.InnerException?.Message ?? exception.Message); + } + + public static void LogInitiativeError(this ILogger logger, Exception exception, string name, Guid opportunityGuid, Guid initiativeGuid) + { + if (exception.InnerException != null) + { + logger.LogInitiativeError(exception.InnerException, SanitizeForLogging(name), opportunityGuid, initiativeGuid); + return; + } + var dictionary = new Dictionary { + { "service", $"strata-continuousimprovement-{typeof(T).Name}" }, + { "initiative", SanitizeForLogging(name) }, + { "opportunityGuid", opportunityGuid }, + { "initiativeGuid", initiativeGuid } + }; + using (logger.BeginScope(dictionary)) + logger.LogError(exception.InnerException ?? exception, exception.InnerException?.Message ?? exception.Message); + } + + public static void LogMappingJobError(this ILogger _logger, Exception ex, JazzDbContext dbContext, string jobName) + => _logger.LogMappingError(ex, dbContext, $"{jobName} Job Failed"); + + private static void LogMappingError(this ILogger _logger, Exception ex, JazzDbContext dbContext, string jobName) + { + using (_logger.BeginScope(dbContext.Database.DbContextDictionary(typeof(T).Name))) + _logger.LogError(ex, jobName); + } + + public static void LogMappingJobInformation(this ILogger _logger, Exception ex, JazzDbContext dbContext, string jobName) + => _logger.LogMappingInformation(ex, dbContext, $"{jobName} Job Failed"); + + private static void LogMappingInformation(this ILogger _logger, Exception ex, JazzDbContext dbContext, string jobName) + { + using (_logger.BeginScope(dbContext.Database.DbContextDictionary(typeof(T).Name))) + _logger.LogInformation(ex, jobName); + } + + public static void LogPerformanceInformation(this ILogger _logger, + string incomingMessage, + [CallerMemberName] string caller = "", + [CallerFilePath] string filePath = "", + [CallerLineNumber] int lineNumber = 0) + { + if (_logger == null) { return; } + + string fileName = Path.GetFileName(filePath); + string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); + + var message = $"[{timestamp}] {fileName}:{lineNumber} ({caller}) - {incomingMessage}"; +#if DEBUG + System.Diagnostics.Debug.WriteLine(message); +#endif + var dictionary = new Dictionary { + { "performance", Microservice + typeof(T).Name } + }; + using (_logger.BeginScope(dictionary)) + _logger.LogInformation(message); + + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/MathExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/MathExtensions.cs new file mode 100644 index 0000000..e8ca6f2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/MathExtensions.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + [ExcludeFromCodeCoverage] + public static class MathExtensions + { + public static double DivideBy(this double value, double divisor) + { + if (divisor == 0.0) + { + return 0.0; + } + return value / divisor; + } + + public static decimal DivideBy(this decimal dividend, decimal divisor) + { + if (divisor == 0) + { + return 0; + } + return dividend / divisor; + } + + public static double DivideBy(this int dividend, int divisor) + { + if (divisor == 0) + { + return 0; + } + + return (double)dividend / divisor; + } + + public static double DivideBy(this int dividend, double divisor) + { + if (divisor == 0.0) + { + return 0; + } + + return dividend / divisor; + } + + /// + /// Median is the middle value in a set of numbers that have been arranged in order from smallest to largest. It's also known as the 50th percentile, meaning that half of the numbers in the set are less than the median and half are greater than it. + /// + public static double Median(this IEnumerable values) + { + if (!values.Any()) { return 0; } + var count = values.Count(); + return (count % 2 == 0) + ? values.OrderBy(x => x).Skip((count / 2) - 1).Take(2).Average() + : values.OrderBy(x => x).ElementAt(count / 2); + } + + /// + /// Median is the middle value in a set of numbers that have been arranged in order from smallest to largest. It's also known as the 50th percentile, meaning that half of the numbers in the set are less than the median and half are greater than it. + /// + public static double Median(this IEnumerable values) + { + if (!values.Any()) { return 0; } + var count = values.Count(); + return (count % 2 == 0) + ? values.OrderBy(x => x).Skip((count / 2) - 1).Take(2).Average() + : values.OrderBy(x => x).ElementAt(count / 2); + } + + /// + /// Calculate weighted average. Will NOT throw dividebyzero exception. + /// + public static double WeightedAverage(this IEnumerable records, Func value, Func weight) + { + // http://stackoverflow.com/questions/2714639/weighted-average-with-linq + records = records as IList ?? records.ToList(); + + var weightSum = records.Sum(weight); + + if (!(weightSum > 0)) return 0; + + var weightedValueSum = records.Sum(x => value(x) * weight(x)); + return weightedValueSum / weightSum; + } + + /// + /// Calculates the Observed/Expected (O/E) ratio with null and zero handling. + /// Returns null if either value is null or if expected is zero. + /// + /// The observed (customer) value. + /// The expected (benchmark) value. + /// The O/E ratio, or null if calculation is not possible. + public static decimal? CalculateOeRatio(decimal? observed, decimal? expected) + { + if (!observed.HasValue || !expected.HasValue || expected.Value == 0) + return null; + + return observed.Value / expected.Value; + } + + /// + /// Calculates the Observed/Expected (O/E) ratio with null and zero handling. + /// + /// The observed (customer) value. + /// The expected (benchmark) value. + /// The O/E ratio, or null if calculation is not possible. + public static double? CalculateOeRatio(double observed, double expected) + { + if (expected == 0) + { + return null; + } + return observed / expected; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/ObjectExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/ObjectExtensions.cs new file mode 100644 index 0000000..747b84f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/ObjectExtensions.cs @@ -0,0 +1,45 @@ +using Strata.ContinuousImprovement.Biz.Encounter.Opportunities; +using System; +using System.Reflection; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class ObjectExtensions + { + public static bool TrySetPropertyValue(this object obj, string propertyName, object value, bool ignoreCase = false) + { + var propertyInfo = ignoreCase + ? obj.GetType().GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) + : obj.GetType().GetProperty(propertyName); + + if (propertyInfo == null) + { + return false; + } + + propertyInfo.SetValue(obj, Convert.ChangeType(value, propertyInfo.PropertyType), null); + + return true; + } + public static bool TryGetPropertyValue(this object src, string propName, out T value, bool ignoreCase = false) + { + var prop = ignoreCase + ? src.GetType().GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) + : src.GetType().GetProperty(propName); + if (prop == null) + { + value = default(T); + return false; + } + + value = (T)prop.GetValue(src, null); + return true; + } + + public static bool IsEquivalent(this OpportunityMetricCohortData data, ENOpportunityMetricData metric) + { + return data.IsBaseline == metric.IsBaseline && data.LevelID == metric.LevelID && data.Month == metric.Month; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/ProblemDefaults.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/ProblemDefaults.cs new file mode 100644 index 0000000..3b4d907 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/ProblemDefaults.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class ProblemDefaults + { + private static readonly Dictionary Defaults = new() + { + [204] = ("https://tools.ietf.org/html/rfc9110#section-15.3.5", "No Content"), + [404] = ("https://tools.ietf.org/html/rfc9110#section-15.5.5", "Not Found") + }; + + public static string ProblemDetailTitle(this int? statusCode) + => Defaults.TryGetValue(statusCode.GetValueOrDefault(-1), out var dflt) + ? dflt.Title + : string.Empty; + + public static string ProblemDetailType(this int? statusCode) + => Defaults.TryGetValue(statusCode.GetValueOrDefault(-1), out var dflt) + ? dflt.Type + : string.Empty; + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/ResourceExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/ResourceExtensions.cs new file mode 100644 index 0000000..eabe60d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/ResourceExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class ResourceExtensions + { + public static string ReadResource(this Assembly assembly, string resourceName) + { + // Format: "{Namespace}.{Folder}.{filename}.{Extension}" + if (!resourceName.StartsWith("Strata.ContinuousImprovement.Biz")) + { + var regex = new Regex(@"Strata.ContinuousImprovement.Biz\..*\." + resourceName, RegexOptions.Compiled | RegexOptions.IgnoreCase); + + resourceName = assembly.GetManifestResourceNames() + .SingleOrDefault(str => regex.IsMatch(str)); + } + if (resourceName is null) return null; + using Stream stream = assembly.GetManifestResourceStream(resourceName)!; + using StreamReader reader = new(stream); + return reader.ReadToEnd(); + } + + public static async Task ReadResourceAsync(this Assembly assembly, string resourceName) + { + // Format: "{ Namespace}.{Folder}.{filename}.{Extension}" + if (!resourceName.StartsWith("Strata.ContinuousImprovement.Biz")) + { + resourceName = assembly.GetManifestResourceNames() + .SingleOrDefault(str => str.EndsWith(resourceName)); + } + if (resourceName is null) return null; + using Stream stream = assembly.GetManifestResourceStream(resourceName)!; + using StreamReader reader = new(stream); + return await reader.ReadToEndAsync(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/StatusMapper.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/StatusMapper.cs new file mode 100644 index 0000000..bc511b0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/StatusMapper.cs @@ -0,0 +1,42 @@ +using Strata.Hangfire.Jazz.Client.Models; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public enum Status + { + Unknown, + Enqueued, + Processing, + Error, + Deleted, + Completed + } + + public static class StatusMapper + { + public static Status Map(this string status) + { + return status switch + { + "Scheduled" or "Enqueued" or nameof(Status.Enqueued) => Status.Enqueued, + "Processing" or nameof(Status.Processing) => Status.Processing, + "Failed" or nameof(Status.Error) => Status.Error, + "Succeeded" or nameof(Status.Completed) => Status.Completed, + "Deleted" or nameof(Status.Deleted) => Status.Deleted, + _ => Status.Unknown, + }; + } + + public static Status Map(this JobInfoDto jobInfo) + => jobInfo.State.Name.Map(); + + public static bool IsSuccessful(this Status status) + => status == Status.Completed || status == Status.Deleted; + + public static bool IsFinalStatus(this Status status) + => status == Status.Error || status == Status.Deleted || status == Status.Completed; + + public static bool IsRunningStatus(this Status status) + => status == Status.Processing || status == Status.Enqueued; + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/StringExtensions.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/StringExtensions.cs new file mode 100644 index 0000000..a413a4d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/StringExtensions.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + public static class StringExtensions + { + /// + /// Parses a comma-separated string of integers and returns a list of valid integers. + /// + /// The comma-separated string to parse. + /// A list of integers parsed from the CSV string. + public static IList ParseIntCsv(this string csv) + { + if (string.IsNullOrEmpty(csv)) + return new List(); + + return csv.Split(',') + .Where(str => int.TryParse(str, out _)) + .Select(int.Parse) + .ToList(); + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/Utilities/StringUtils.cs b/src/Strata.ContinuousImprovement.Biz/Utilities/StringUtils.cs new file mode 100644 index 0000000..a2e973b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/Utilities/StringUtils.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Strata.ContinuousImprovement.Biz.Utilities +{ + [ExcludeFromCodeCoverage] + public static class StringUtils + { + public static string SplitCamelCase(this string input) + { + if (string.IsNullOrEmpty(input)) { return input; } + + const string pattern = "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-r + t-z])|[A-Z](?=[A-Z][s][a-z]))"; + + if (input.StartsWith("FY", StringComparison.Ordinal)) + { // hack for FY2012, etc + return "FY " + Regex.Replace(input.Remove(0, 2), pattern, "$1 "); + } + return Regex.Replace(input, pattern, "$1 "); + } + + public static string TightenUp(this string input) + { + var crlf = Environment.NewLine; + // clean up any black lines + input = Regex.Replace(input, $@"\s*{crlf}\s*{crlf}", crlf); + // clean up line straggling semi-colons + return Regex.Replace(input, $@"\s*{crlf}\s*;", ";"); + } + + public static string YesNo(this bool input) => input ? "Yes" : "No"; + + public static IEnumerable SplitCSV(this string aString) => + aString.Split(',') + .Where(str => int.TryParse(str, out var x) && x != 0) + .Distinct() + .Select(int.Parse); + + public static IEnumerable GetNodeNames(this string nodes) + => nodes.Selections().Select(x => x.Name); + + public static string ToMoment(this string aString) + => DateTime.TryParse(aString, out var date) ? date.ToString("yyyyMMdd") : ""; + + public static DateTime ToInitiativeEffectiveDate(this string aString) + { + var date = DateTime.ParseExact(Regex.Replace(aString, @"[^\d]", ""), "yyyyMMdd", CultureInfo.InvariantCulture); + return new DateTime(date.Year, date.Month, 1); + } + + public static string Maxlength(this string aString, int maxlength) + => aString.Length <= maxlength ? aString : aString.Substring(0, maxlength); + + public static bool AsBoolean(this string aString) + => aString.Maxlength(5).Equals("true", StringComparison.CurrentCultureIgnoreCase); + + public static decimal AppVersion(this string aString) + => decimal.TryParse(aString, out var result) ? result : decimal.MinValue; + + public static String ToCSVList(this IEnumerable items) + => ToSeperatedList(items, ",", s => s.ToString()); + + public static String ToSeperatedList(this IEnumerable items, String delimiter, Func selector) + { + return string.Join(delimiter, items.Select(selector).ToArray()); + } + + public static String IsNotSpecified(this string aString) + => string.IsNullOrEmpty(aString) ? Constants.NOT_SPECIFIED : aString; + + public static string SnowflakeSyntax(this string sql) + { + var result = Regex.Replace(sql.Replace("@", ":").Replace("--", ""), + @"IN \((:[^)]*)\)", "IN $1", + RegexOptions.IgnoreCase | RegexOptions.Multiline); + result = Regex.Replace(Regex.Replace(result, @"DATEPART\((MONTH|DAY|YEAR),\s+", "$1("), + @"\[|\]", ""); + return result; + } + } + + [ExcludeFromCodeCoverage] + public static class GuidUtils + { + public static bool CheckGuidFormat(this Guid guid) + => !string.IsNullOrEmpty(guid.ToString()) && Guid.TryParse(guid.ToString(), out _); + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/DatabaseFunctionService.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/DatabaseFunctionService.cs new file mode 100644 index 0000000..9c125c8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/DatabaseFunctionService.cs @@ -0,0 +1,19 @@ +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation +{ + public class DatabaseFunctionService: IDatabaseFunctionService + { + public Expression> SplitString(IEnumerable entityIds) + { + return m => SqlFunctions.Split(m.EntityId, ",").Any(entity => entityIds.Contains(entity.Value)); + } + } +} + diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/DimCaseType.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/DimCaseType.cs new file mode 100644 index 0000000..fa5afff --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/DimCaseType.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation +{ + [Table("DimCaseType", Schema = "dss")] + public class DimCaseType + { + [Key] + public int CaseTypeId { get; set; } + public Guid MemberGUID { get; set; } + public string Name { get; set; } + public int CaseTypeFamilyId { get; set; } + public Guid ConfigurationGUID { get; set; } + public string CaseTypeconfigJSON { get; set; } + public bool IsAllEntity { get; set; } + public int ClinicalIndicatorId { get; set; } + public int CaseTypeCategory { get; set; } + public string ChargeCodeGroupJSON { get; set; } = "[]"; + public long ExplorationPopulationId { get; set; } + + public DimCaseType() + { + MemberGUID = Guid.NewGuid(); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/DimEntity.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/DimEntity.cs new file mode 100644 index 0000000..e44942b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/DimEntity.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation +{ + [Table("DimEntity", Schema = "fw")] + public class DimEntity + { + [Key] + public int EntityID { get; set; } + public Guid MemberGUID { get; set; } + public string Code { get; set; } + public string Description { get; set; } + public int SortOrder { get; set; } + public short EntityType { get; set; } + public bool IsActive { get; set; } + public string Name { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/IDatabaseFunctionService.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/IDatabaseFunctionService.cs new file mode 100644 index 0000000..badeb8c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/IDatabaseFunctionService.cs @@ -0,0 +1,12 @@ +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation +{ + public interface IDatabaseFunctionService + { + Expression> SplitString(IEnumerable entityIds); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/Filters/UVFilterOptions.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/Filters/UVFilterOptions.cs new file mode 100644 index 0000000..6e8b459 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/Filters/UVFilterOptions.cs @@ -0,0 +1,13 @@ +using Strata.ContinuousImprovement.Biz.Filter; +using System.Collections.Generic; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters +{ + public class UVFilterOptions + { + public IEnumerable CaseTypes { get; set; } + public IEnumerable CostDrivers { get; set; } + public IEnumerable Entities { get; set; } + public IEnumerable ServiceLines { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/Filters/UVFilters.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/Filters/UVFilters.cs new file mode 100644 index 0000000..4ee3f24 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/Filters/UVFilters.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters +{ + public class UVFilters : FiltersOpportunity + { + public IEnumerable CaseTypeGuids { get; set; } + public IEnumerable CostDrivers { get; set; } + public IEnumerable EntityIds { get; set; } + public IEnumerable ServiceLineIds { get; set; } + + public UVFilters() : base() + { + CaseTypeGuids = new List(); + CostDrivers = new List(); + EntityIds = new List(); + ServiceLineIds = new List(); + } + + public override IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = base.GetFilters(context).ToList(); + + if (!string.IsNullOrEmpty(OpportunitySearch)) + { + filters.Add(m => m.OpportunityKey.Contains(OpportunitySearch) + || m.CaseTypeName.Contains(OpportunitySearch) + || m.ServiceLineName.Contains(OpportunitySearch) + || m.EntityName.Contains(OpportunitySearch) + || m.CostDriver.Contains(OpportunitySearch)); + } + + if (CaseTypeGuids.Any()) + filters.Add(m => CaseTypeGuids.Contains(m.CaseTypeGuid)); + + if (CostDrivers.Any()) + filters.Add(m => CostDrivers.Contains(m.CostDriver)); + + if (EntityIds.Any()) + filters.Add(context.DatabaseFunctionService.SplitString(EntityIds)); + + if (ServiceLineIds.Any()) + filters.Add(m => ServiceLineIds.Contains(m.ServiceLineId.ToString())); + + return filters; + } + + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/IUVOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/IUVOpportunityService.cs new file mode 100644 index 0000000..55a5cf4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/IUVOpportunityService.cs @@ -0,0 +1,18 @@ +using ClosedXML.Excel; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities +{ + public interface IUVOpportunityService : IOpportunityService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + + Task ExportAsync(SortOptions sortOptions, UVFilters filters, CancellationToken cancellationToken); + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/UVOpportunity.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/UVOpportunity.cs new file mode 100644 index 0000000..34fcb30 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/UVOpportunity.cs @@ -0,0 +1,21 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities +{ + [Table("viewVariationOpportunity", Schema = "cci")] + public class UVOpportunity : Opportunity + { + public Guid CaseTypeGuid { get; set; } + public string CaseTypeName { get; set; } + public string CostDriver { get; set; } + public int Cases { get; set; } + public string EntityId { get; set; } + public string EntityName { get; set; } + public int ServiceLineId { get; set; } + public string ServiceLineName { get; set; } + public double VariationPerCase { get; set; } + public long OpportunityId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/UVOpportunityService.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/UVOpportunityService.cs new file mode 100644 index 0000000..5ce226e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/UVOpportunityService.cs @@ -0,0 +1,93 @@ +using ClosedXML.Excel; +using Microsoft.EntityFrameworkCore; +using MoreLinq; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Export; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Initiatives; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters; +using Strata.CoreLib.Claims; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities +{ + public class UVOpportunityService : OpportunityService, IUVOpportunityService + { + public UVOpportunityService(IAsyncDbContextFactory dbContextFactory, CentralDbContext centralDbContext, + IClaimsPrincipalAccessor claimsPrincipalAccessor, + IInitiativeService initiativeService, IUpdateFactOpportunitySavingsService updateFactOpportunitySavingsService) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor, + initiativeService, updateFactOpportunitySavingsService) + { + } + + public async Task ExportAsync(SortOptions sortOptions, UVFilters filters, CancellationToken cancellationToken) + { + var exportOptions = this.GetExportOptions(); + + // get data + var data = (await this.GetAllAsync(sortOptions, filters, cancellationToken)).Select(opp => new + { + Opportunity = opp.OpportunityKey, + opp.VariationPerCase, + opp.IdentifiedSavings, + opp.Cases, + CaseType = opp.CaseTypeName, + opp.CostDriver, + Entity = opp.EntityName, + ServiceLine = opp.ServiceLineName, + opp.Initiative, + IsHidden = opp.IsHidden ? 1 : 0, + opp.Note + }); + + // configure column formats + exportOptions.AddColumnOptions("Variation Per Case", "Currency"); + exportOptions.AddColumnOptions("Identified Savings", "Currency2Dec"); + exportOptions.AddColumnOptions("Cases", "Accounting"); + exportOptions.AddColumnOptions("Is Hidden", "\"Yes\";-;\"No\""); + + return ExportUtils.CreateExcelWorkbook(data, exportOptions); + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return null; + + var configuredOpportunities = dbContext.Set().Where(vo => vo.ConfigurationGuid == configurationGuid); + + var caseTypes = configuredOpportunities + .Select(vo => new FilterMember { Id = vo.CaseTypeGuid.ToString(), Text = vo.CaseTypeName }) + .Distinct().OrderBy(ct => ct.Text); + + var entities = configuredOpportunities + .Select(vo => new FilterMember { Id = vo.EntityId.ToString(), Text = vo.EntityName }). + Distinct().OrderBy(oa => oa.Text); + + var costDrivers = configuredOpportunities + .Select(vo => new FilterMember { Id = vo.CostDriver, Text = vo.CostDriver }) + .Distinct().OrderBy(oa => oa.Text); + + var serviceLines = configuredOpportunities + .Select(vo => new FilterMember { Id = vo.ServiceLineId.ToString(), Text = vo.ServiceLineName }) + .Distinct().OrderBy(oa => oa.Text); + + return new UVFilterOptions + { + CaseTypes = await caseTypes.ToListAsync(cancellationToken), + Entities = await entities.ToListAsync(cancellationToken), + CostDrivers = await costDrivers.ToListAsync(cancellationToken), + ServiceLines = await serviceLines.ToListAsync(cancellationToken) + }; + } + + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/VariationOpportunityDataModel.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/VariationOpportunityDataModel.cs new file mode 100644 index 0000000..971eeb4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/Opportunities/VariationOpportunityDataModel.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities +{ + [Table("VariationOpportunity", Schema = "cci")] + public class VariationOpportunityDataModel + { + [Key] + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public short YearConsidered { get; set; } + public string CostDriver { get; set; } + public byte SavingsLevel { get; set; } + public double Savings { get; set; } + public double SavingsMedian { get; set; } + public double SavingsTarget { get; set; } + public int TotalCases { get; set; } + public double TotalCost { get; set; } + public double TotalVariableCost { get; set; } + public double CostMedian { get; set; } + public double CostMedicare { get; set; } + public double VariableCostMedian { get; set; } + public double VariableCostMedicare { get; set; } + public string Note { get; set; } + public DateTime DateModified { get; set; } + public DateTime DateCreated { get; set; } + public Guid ConfigurationGuid { get; set; } + public bool IsHidden { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/CaseTypeCategories.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/CaseTypeCategories.cs new file mode 100644 index 0000000..59ffa51 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/CaseTypeCategories.cs @@ -0,0 +1,11 @@ +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes +{ + public enum CaseTypeCategories + { + MSDRG = 0, + APRDRG = 1, + PatientPopulation = 2, + CPTCode = 3, + ExplorationPopulation = 4 + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/CaseTypeTaggingMode.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/CaseTypeTaggingMode.cs new file mode 100644 index 0000000..5755dab --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/CaseTypeTaggingMode.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes +{ + public enum CaseTypeTaggingMode + { + InitialSetup = 0, + Savings = 1, + Tracking = 2 + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/Filters/UVCaseTypeFilters.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/Filters/UVCaseTypeFilters.cs new file mode 100644 index 0000000..666a66e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/Filters/UVCaseTypeFilters.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Filter.Expressions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes.Filters +{ + public class UVCaseTypeFilters:IFilters + { + public Guid ConfigurationGuid { get; set; } + public double? IdentifiedSavings { get; set; } + public IEnumerable CaseTypeGuids { get; set; } + public IEnumerable ServiceLineIds { get; set; } + + public UVCaseTypeFilters() : base() + { + ConfigurationGuid = Guid.Empty; + CaseTypeGuids = new List(); + ServiceLineIds = new List(); + } + + public IEnumerable>> GetFilters(JazzDbContext context) + { + var filters = new List>>(); + filters.AddConfigurationGuidFilter(ConfigurationGuid); + filters.AddIdentifiedSavingsTotalFilter(IdentifiedSavings); + + if (CaseTypeGuids.Any()) + filters.Add(m => CaseTypeGuids.Contains(m.CaseTypeGuid)); + + if (ServiceLineIds.Any()) + filters.Add(m => ServiceLineIds.Contains(m.ServiceLineId.ToString())); + + return filters; + } + + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/IUVCaseTypeService.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/IUVCaseTypeService.cs new file mode 100644 index 0000000..1f6bd6a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/IUVCaseTypeService.cs @@ -0,0 +1,16 @@ +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public interface IUVCaseTypeService : IJazzDbService + { + Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken); + Task AddPatientPopulationCaseTypes(Guid configurationGuid, string userName, List patientPopulations, CancellationToken cancellationToken); + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/UVCaseType.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/UVCaseType.cs new file mode 100644 index 0000000..cc277bd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/UVCaseType.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using Strata.ContinuousImprovement.Biz.Generics; +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes +{ + [Keyless] + [Table("viewVariationCaseType", Schema = "cci")] + public class UVCaseType : IWithConfigurationGuid, IWithIdentifiedSavingsTotal + { + public int CaseTypeId { get; set; } + public Guid CaseTypeGuid { get; set; } + public Guid ConfigurationGuid { get; set; } + public string CaseTypeName { get; set; } + public int ServiceLineId { get; set; } + public string ServiceLineName { get; set; } + public double IdentifiedSavingsTotal { get; set; } + public int OpportunitiesCount { get; set; } + public int CaseTypeCategory { get; set; } + public string CostDriver { get; set; } + public Guid AssociatedOpportunityGuid { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/UVCaseTypeService.cs b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/UVCaseTypeService.cs new file mode 100644 index 0000000..86fcc30 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/UtilizationVariation/VariationCaseTypes/UVCaseTypeService.cs @@ -0,0 +1,171 @@ +using DocumentFormat.OpenXml.Drawing; +using Hangfire; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Strata.ContinuousImprovement.Biz.Configuration; +using Strata.ContinuousImprovement.Biz.DbContexts; +using Strata.ContinuousImprovement.Biz.Filter; +using Strata.ContinuousImprovement.Biz.Generics; +using Strata.ContinuousImprovement.Biz.Notification; +using Strata.ContinuousImprovement.Biz.Pagination; +using Strata.ContinuousImprovement.Biz.UtilizationVariation.Opportunities.Filters; +using Strata.CoreLib.Claims; +using Strata.Hangfire.Jazz.Client; +using Strata.Hangfire.Jazz.Client.Models; +using Strata.Schema.Client; +using Strata.SqlTools.Configuration.Common.AsyncFactory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.UtilizationVariation.VariationCaseTypes +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Standard abbreviation")] + public class UVCaseTypeService : JazzDbService, IUVCaseTypeService + { + private readonly ISchemaServiceClient _schemaServiceClient; + private readonly IHubContext _notificationHubContext; + private readonly IJazzHangfireServiceClient _hangfireServiceClient; + private readonly ILogger _logger; + private readonly IConfigurationService _configurationService; + + public UVCaseTypeService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, IClaimsPrincipalAccessor claimsPrincipalAccessor) + : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor) + { + } + + public UVCaseTypeService(IAsyncDbContextFactory dbContextFactory, + CentralDbContext centralDbContext, IClaimsPrincipalAccessor claimsPrincipalAccessor, + ISchemaServiceClient schemaServiceClient, + IHubContext notificationHubContext, + IJazzHangfireServiceClient hangfireServiceClient, + ILogger logger, + IConfigurationService configurationService) : base(dbContextFactory, centralDbContext, claimsPrincipalAccessor) + { + _schemaServiceClient = schemaServiceClient; + _notificationHubContext = notificationHubContext; + _hangfireServiceClient = hangfireServiceClient; + _logger = logger; + _configurationService = configurationService; + } + + public async Task AddPatientPopulationCaseTypes(Guid configurationGuid, string userName, List patientPopulations, CancellationToken cancellationToken) + { + var hub = _notificationHubContext.Clients.Group(userName); + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return false; + + var patientPopulationDimension = await _schemaServiceClient.GetDimensionByGlobalIdAsync("Clinical Indicator"); + var members = await _schemaServiceClient.GetDimensionMembersFromHierarchyPathsAsync(patientPopulationDimension.DimensionGuid, patientPopulations, cancellationToken); + var patientPopCaseTypes = new List(); + var configuration = await _configurationService.GetConfigurationAsync(configurationGuid, cancellationToken); + + foreach (var member in members) + { + var patientPopCaseType = new DimCaseType() + { + ConfigurationGUID = Guid.Empty, // case types are configured for all configurations + Name = member.Name, + CaseTypeconfigJSON = "{\"PhysicianMinVolume\":0}", + IsAllEntity = true, + ClinicalIndicatorId = Convert.ToInt32(member.Id), + CaseTypeCategory = (int)CaseTypeCategories.PatientPopulation, + }; + + patientPopCaseTypes.Add(patientPopCaseType); + } + + var caseTypeContext = dbContext.Set(); + + var newCaseTypes = patientPopCaseTypes + .Where(newCaseType => !caseTypeContext.Any(existingCaseType => + existingCaseType.ClinicalIndicatorId == newCaseType.ClinicalIndicatorId + && existingCaseType.IsAllEntity == newCaseType.IsAllEntity + && existingCaseType.CaseTypeCategory == newCaseType.CaseTypeCategory)).ToList(); + var existingCaseTypes = patientPopCaseTypes.Except(newCaseTypes).ToList(); + try + { + // Save new case types + await caseTypeContext.AddRangeAsync(newCaseTypes); + await dbContext.SaveChangesAsync(cancellationToken); + + if (newCaseTypes.Any()) + { + // Run jobs to retag encounter case types and recalculate UV savings with new case types + await RunCaseTypeTaggingJob(cancellationToken); + await RunSavingsForNewCaseTypesJob(configurationGuid, newCaseTypes, cancellationToken); + await hub.SendPatientPopulationCaseTypeCreateSuccess(userName, newCaseTypes.Count.ToString()); + } + + if (existingCaseTypes.Count() > 0) + { + await hub.SendPatientPopulationCaseTypeAlreadyExist(userName + "SendPatientPopulationCaseTypeAlreadyExist", existingCaseTypes.Count().ToString()); + } + + return true; + } + catch (Exception ex) + { + await hub.SendPatientPopulationCaseTypeCreateError(userName); + + // If there's an error clear out added types - users can't delete existing or re-add duplicate to trigger tagging/savings. + caseTypeContext.RemoveRange(newCaseTypes); + await dbContext.SaveChangesAsync(cancellationToken); + + _logger.LogError(ex, "Error adding patient population case types."); + return false; + } + } + + public async Task GetFilterOptionsAsync(Guid configurationGuid, CancellationToken cancellationToken) + { + await using var dbContext = GetDbContext(cancellationToken).Result; + if (dbContext == null) return (UVFilterOptions)null; + + var caseTypeContext = dbContext.Set(); + var configuredCaseTypes = caseTypeContext.Where(ct => ct.ConfigurationGuid == configurationGuid); + + var caseTypes = configuredCaseTypes + .Select(vo => new FilterMember { Id = vo.CaseTypeGuid.ToString(), Text = vo.CaseTypeName }) + .Distinct().OrderBy(ct => ct.Text); + + var serviceLines = configuredCaseTypes + .Select(vo => new FilterMember { Id = vo.ServiceLineId.ToString(), Text = vo.ServiceLineName }) + .Distinct().OrderBy(oa => oa.Text); + + return new UVFilterOptions() + { + CaseTypes = await caseTypes.ToListAsync(cancellationToken), + ServiceLines = await serviceLines.ToListAsync(cancellationToken) + }; + } + + private async Task RunCaseTypeTaggingJob(CancellationToken cancellationToken) + { + var caseTypeTaggingJob = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.Variation.PatientEncounterSummaryUpdateCaseTypeIdTask, Strata.CS.Jazz.Biz.CCI", + ContextData = JsonConvert.SerializeObject(new { Mode = (int)CaseTypeTaggingMode.InitialSetup }) + }; + + await _hangfireServiceClient.EnqueueJobAsync(caseTypeTaggingJob, cancellationToken); + } + + private async Task RunSavingsForNewCaseTypesJob(Guid configurationGuid, IEnumerable newCaseTypes, CancellationToken cancellationToken) + { + var runSavingsJob = new EnqueueJobDto + { + TaskName = "Strata.CS.Jazz.Biz.CCI.Tasks.Variation.RunVariationSavingsTask, Strata.CS.Jazz.Biz.CCI", + ContextData = JsonConvert.SerializeObject(new { configurationGuid = configurationGuid, caseTypeGuids = newCaseTypes.Select(x => x.MemberGUID) }) + }; + + await _hangfireServiceClient.EnqueueJobAsync(runSavingsJob, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Biz/WorkbookClient/WorkbookClientMapper.cs b/src/Strata.ContinuousImprovement.Biz/WorkbookClient/WorkbookClientMapper.cs new file mode 100644 index 0000000..44c907e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/WorkbookClient/WorkbookClientMapper.cs @@ -0,0 +1,17 @@ +using Riok.Mapperly.Abstractions; +using Strata.Analytics.Models.Workbooks; +using Strata.ContinuousImprovement.Biz.OpportunityTypeWorkbooks; +using Strata.ContinuousImprovement.Biz.OpportunityWorkbooks; +using Strata.DecisionSupport.Models.Jazz; + +namespace Strata.ContinuousImprovement.Biz.WorkbookClient +{ + [Mapper(UseDeepCloning = true)] + public static partial class WorkbookClientMapper + { + public static partial WorkbookClientInfo Mapped(this Workbook src); + public static partial OpportunityTypeWorkbookClientInfo Mapped(this OpportunityTypeWorkbook src); + public static partial OpportunityWorkbookClientInfo Mapped(this OpportunityWorkbook src); + } + +} diff --git a/src/Strata.ContinuousImprovement.Biz/runtimeconfig.template.json b/src/Strata.ContinuousImprovement.Biz/runtimeconfig.template.json new file mode 100644 index 0000000..9cbaf60 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Biz/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.Drawing.EnableUnixSupport": true + } +} diff --git a/src/Strata.ContinuousImprovement.Client/ChargeCodeDetailInfo.cs b/src/Strata.ContinuousImprovement.Client/ChargeCodeDetailInfo.cs new file mode 100644 index 0000000..09cc6da --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/ChargeCodeDetailInfo.cs @@ -0,0 +1,10 @@ +namespace Strata.ContinuousImprovement.Client +{ + public class ChargeCodeDetailInfo + { + public int StrataId { get; set; } + public Guid OpportunityGuid { get; set; } + public bool IsBaseline { get; set; } + public bool IsUpdate { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/ChargeCodeOpportunityJobRequestInfo.cs b/src/Strata.ContinuousImprovement.Client/ChargeCodeOpportunityJobRequestInfo.cs new file mode 100644 index 0000000..dc243b5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/ChargeCodeOpportunityJobRequestInfo.cs @@ -0,0 +1,11 @@ +namespace Strata.ContinuousImprovement.Client +{ + /// + /// Contains information required to request a charge code opportunity job. + /// + public class ChargeCodeOpportunityJobRequestInfo + { + public int StrataId { get; set; } + public Guid OpportunityGuid { get; set; } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Client/ClientNetworkOpportunityDeleteInfo.cs b/src/Strata.ContinuousImprovement.Client/ClientNetworkOpportunityDeleteInfo.cs new file mode 100644 index 0000000..f153c11 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/ClientNetworkOpportunityDeleteInfo.cs @@ -0,0 +1,17 @@ +namespace Strata.ContinuousImprovement.Client +{ + public class ClientNetworkOpportunityDeleteInfo + { + public Guid DatabaseGuid { get; set; } + public Guid Configuration { get; set; } + public Guid Id { get; set; } + + public ClientNetworkOpportunityDeleteInfo() { } + public ClientNetworkOpportunityDeleteInfo(Guid databaseGuid, Guid configuration, Guid id) + { + DatabaseGuid = databaseGuid; + Configuration = configuration; + Id = id; + } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/ClientNetworkOpportunityResultDto.cs b/src/Strata.ContinuousImprovement.Client/ClientNetworkOpportunityResultDto.cs new file mode 100644 index 0000000..cd14286 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/ClientNetworkOpportunityResultDto.cs @@ -0,0 +1,11 @@ +namespace Strata.ContinuousImprovement.Client +{ + public class ClientNetworkOpportunityResultDto + { + public int NetworkOpportunityId { get; set; } + public Guid ConfigurationGuid { get; set; } + public Guid OpportunityGuid { get; set; } + public Guid InitiativeGuid { get; set; } + public string Name { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/ConfigurationInfo.cs b/src/Strata.ContinuousImprovement.Client/ConfigurationInfo.cs new file mode 100644 index 0000000..632be91 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/ConfigurationInfo.cs @@ -0,0 +1,10 @@ +namespace Strata.ContinuousImprovement.Client +{ + public class ConfigurationInfo + { + public Guid ConfigurationGuid { get; set; } + public string Name { get; set; } + public byte DisplayOrder { get; set; } + public short FiscalYearId { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/ContinuousImprovementService.cs b/src/Strata.ContinuousImprovement.Client/ContinuousImprovementService.cs new file mode 100644 index 0000000..f271729 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/ContinuousImprovementService.cs @@ -0,0 +1,152 @@ +using Strata.ApiCommunication.Http; +using Strata.DecisionSupport.Models.Encounters; +using Strata.DecisionSupport.Models.Jazz; +using Strata.DecisionSupport.Models.Queries; +using Jazz = Strata.DecisionSupport.Models.Jazz; + +namespace Strata.ContinuousImprovement.Client +{ + public partial class ContinuousImprovementService : IContinuousImprovementService + { + private readonly HttpClient _httpClient; + + public ContinuousImprovementService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task DeleteOpportunityLink(Guid databaseGuid, Guid configurationGuid, Guid opportunityGuid, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Delete, $"api/v1/client-network-opportunity-link/delete-opportunity-link"); await request.SetContentAsJsonAsync(new ClientNetworkOpportunityDeleteInfo(databaseGuid, configurationGuid, opportunityGuid), cancellationToken); await _httpClient.SendAsync(request, cancellationToken); } + + public async Task DeleteInitiativeLink(Guid databaseGuid, Guid configurationGuid, Guid initiativeGuid, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Delete, $"api/v1/client-network-opportunity-link/delete-initiative-link"); await request.SetContentAsJsonAsync(new ClientNetworkOpportunityDeleteInfo(databaseGuid, configurationGuid, initiativeGuid), cancellationToken); await _httpClient.SendAsync(request, cancellationToken); } + + public async Task RolloverOpportunityLink(Guid databaseGuid, + Guid sourceConfigurationGuid, Guid sourceOpportunityGuid, + Guid rolloverConfigurationGuid, Guid rolloverOpportunityGuid, Guid rolloverInitiativeGuid, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/client-network-opportunity-link"); await request.SetContentAsJsonAsync(new RolloverOpportunityInfo(databaseGuid, sourceConfigurationGuid, sourceOpportunityGuid, rolloverConfigurationGuid, rolloverOpportunityGuid, rolloverInitiativeGuid), cancellationToken); return await _httpClient.SendAsync(request, cancellationToken); } + + public async Task RolloverOpportunity(RolloverOpportunityInfo rolloverOpportunityInfo, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/client-network-opportunity-link"); await request.SetContentAsJsonAsync(rolloverOpportunityInfo, cancellationToken); return await _httpClient.SendAsync(request, cancellationToken); } + + #region Decision Support Encounter queries + + public async Task> DecisionSupportMsdrgEncounterInfo(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default) + => await DecisionSupportEncounters(encounterInfo, cancellationToken); + + public async Task> DecisionSupportAprdrgEncounterInfo(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default) + => await DecisionSupportEncounters(encounterInfo, cancellationToken); + + public async Task> DecisionSupportCptCodeEncounterInfo(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default) + => await DecisionSupportEncounters(encounterInfo, cancellationToken); + + public async Task> DecisionSupportPatientPopulationEncounterInfo(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default) + => await DecisionSupportEncounters(encounterInfo, cancellationToken); + + public async Task> ExplorationEncounterInfo(long explorationId, Guid configurationGuid, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/exploration-populations/{explorationId}/encounters?configurationGuid={configurationGuid}"); return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task GetExplorationPopulationById(long explorationId, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/exploration-populations/info/{explorationId}"); + return await _httpClient.SendAsync(request, cancellationToken); + } + +#pragma warning disable S4457 // Split this method into two, one handling parameter check and the other handling the asynchronous code + private async Task> DecisionSupportEncounters(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default) +#pragma warning restore S4457 // Split this method into two, one handling parameter check and the other handling the asynchronous code + { if (encounterInfo.EntityIDs == null || !encounterInfo.EntityIDs.Any()) { throw new ArgumentNullException(nameof(encounterInfo)); } return await DecisionSupportEncountersAsync(encounterInfo, cancellationToken); + } + + private async Task> DecisionSupportEncountersAsync(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/encounters"); await request.SetContentAsJsonAsync(encounterInfo, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); } + + public async Task> DecisionSupportEncounterCostInfo(EncounterCostQueryInfo encounterCostInfo, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/encounters/cost-info"); await request.SetContentAsJsonAsync(encounterCostInfo, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); } + + public async Task> DecisionSupportDepartmentEncounterUnitOfServiceInfo(DepartmentEncounterUnitOfServiceQueryInfo departmentEncounterUnitOfServiceQueryInfo, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/encounters/department-unit-of-service-info"); await request.SetContentAsJsonAsync(departmentEncounterUnitOfServiceQueryInfo, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); } + + public async Task> DecisionSupportEncounterUnitOfServiceInfo(EncounterUnitOfServiceQueryInfo encounterUnitOfServiceQueryInfo, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/encounters/unit-of-service-info"); await request.SetContentAsJsonAsync(encounterUnitOfServiceQueryInfo, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> DecisionSupportVariationEncounterChargeInfo(VariationEncounterChargeQueryInfo variationEncounterChargeQueryInfo, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/encounters/variation-encounter-charge-info"); await request.SetContentAsJsonAsync(variationEncounterChargeQueryInfo, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> DecisionSupportLOSOpportunityInfo(LOSOpportunityQueryInfo losOpportunityInfoqueryInfo, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/encounters/los-opportunity-info"); await request.SetContentAsJsonAsync(losOpportunityInfoqueryInfo, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> DecisionSupportEncounterChargeInfo(EncounterChargeQueryInfo encounterChargeQueryInfo, CancellationToken cancellationToken = default) + { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/encounters/encounter-charge-info"); await request.SetContentAsJsonAsync(encounterChargeQueryInfo, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); + } + #endregion + + #region Opportunity Metric Cohort Data + + public async Task> ChargeCodeOpportunityMetricCohortInfo(OpportunityMetricCohortOptions options, CancellationToken cancellationToken) { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/charge-code-opportunities/opportunity-metric-cohort-data"); await request.SetContentAsJsonAsync(options, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> EncounterOpportunityMetricCohortInfo(OpportunityMetricCohortOptions options, CancellationToken cancellationToken) { var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/encounter-opportunities/opportunity-metric-cohort-data"); await request.SetContentAsJsonAsync(options, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); + } + + #endregion + + [Obsolete] + public async Task CreateChargeCodeOpportunityDetails(ChargeCodeDetailInfo detailInfo, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/charge-code-opportunity-details/create"); + await request.SetContentAsJsonAsync(detailInfo, cancellationToken); + await _httpClient.SendAsync(request, cancellationToken); + } + #region Workbook Opportunity + public async Task> GetAllWorkbooks(CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Get, "api/v1/client-opportunity-workbooks/workbooks"); return await _httpClient.SendAsync>(request, cancellationToken); } + public async Task> GetOpportunityWorkbooks(Guid opportunityGuid, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/client-opportunity-workbooks/{opportunityGuid}"); return await _httpClient.SendAsync>(request, cancellationToken); } + public async Task> GetOpportunityTypeWorkbooks(int opportunityType, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/client-opportunity-type-workbooks/{opportunityType}"); return await _httpClient.SendAsync>(request, cancellationToken); } + public async Task> CreateOpportunityWorkbooks(IEnumerable opportunityWorkbooks, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/client-opportunity-workbooks"); await request.SetContentAsJsonAsync(opportunityWorkbooks, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); } + public async Task> CreateOpportunityTypeWorkbooks(IEnumerable opportunityTypeWorkbooks, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Post, "api/v1/client-opportunity-type-workbooks"); await request.SetContentAsJsonAsync(opportunityTypeWorkbooks, cancellationToken); return await _httpClient.SendAsync>(request, cancellationToken); } + public async Task DeleteOpportunityWorkbook(Guid opportunityGuid, int workbookId, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Delete, $"api/v1/client-opportunity-workbooks/{opportunityGuid}/workbooks/{workbookId}"); + return await _httpClient.SendAsync(request, cancellationToken); } + public async Task DeleteOpportunityTypeWorkbook(int opportunityType, int workbookId, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Delete, $"api/v1/client-opportunity-type-workbooks/{opportunityType}/workbooks/{workbookId}"); return await _httpClient.SendAsync(request, cancellationToken); } + + #endregion + + public async Task RefreshChargeCodeOpportunityBaselineDetailsAsync(int strataId, Guid opportunityGuid, CancellationToken cancellationToken = default) + { + return await RefreshChargeCodeOpportunityDetailsAsync(strataId, opportunityGuid, "api/v1/charge-code-opportunity-details/refresh-baseline-details", cancellationToken); + } + + public async Task RefreshChargeCodeOpportunityTrackingDetailsAsync(int strataId, Guid opportunityGuid, CancellationToken cancellationToken = default) + { + return await RefreshChargeCodeOpportunityDetailsAsync(strataId, opportunityGuid, "api/v1/charge-code-opportunity-details/refresh-tracking-details", cancellationToken); + } + + internal async Task RefreshChargeCodeOpportunityDetailsAsync(int strataId, Guid opportunityGuid, string url, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Post, url); + var jobRequestInfo = new ChargeCodeOpportunityJobRequestInfo + { + StrataId = strataId, + OpportunityGuid = opportunityGuid + }; + await request.SetContentAsJsonAsync(jobRequestInfo, cancellationToken); + return await _httpClient.SendAsync(request, cancellationToken); + } + + + public async Task GetBackgroundClientJobStatus(string jobId, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/background-client-job/{jobId}/status"); + return await _httpClient.SendAsync(request, cancellationToken); + } + + public async Task PopulateQviMetrics(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Post, $"api/v1/quality-variation-item-opportunities/populate-qvi-metrics?configurationGuid={configurationGuid}&isBaseline={isBaseline}"); + return await _httpClient.SendAsync(request, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/ContinuousImprovementServiceCollectionExtension.cs b/src/Strata.ContinuousImprovement.Client/ContinuousImprovementServiceCollectionExtension.cs new file mode 100644 index 0000000..05d55a8 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/ContinuousImprovementServiceCollectionExtension.cs @@ -0,0 +1,35 @@ +#if NETCOREAPP +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Strata.ContinuousImprovement.Client +{ + [ExcludeFromCodeCoverage] + public static class ContinuousImprovementServiceCollectionExtension + { + /// + /// Registers an instance of an to the given + /// + /// An that can be used to configure the client. + public static IHttpClientBuilder AddContinuousImprovementService(this IServiceCollection services) + { + return services.AddHttpClientWithStrataHandlers((provider, client) => + { + var config = provider.GetService(); + + var endpoint = config.GetValue("endPoints:continuousimprovement"); + if (string.IsNullOrWhiteSpace(endpoint)) + { + throw new InvalidOperationException( + "Endpoint for the Continuous Improvement service was not found in the configuration with the key of 'endPoints:continuousimprovement'"); + } + + client.BaseAddress = new Uri(endpoint); + }); + } + + } +} +#endif \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Client/IContinuousImprovementService.cs b/src/Strata.ContinuousImprovement.Client/IContinuousImprovementService.cs new file mode 100644 index 0000000..bbe7577 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/IContinuousImprovementService.cs @@ -0,0 +1,45 @@ +using Strata.DecisionSupport.Models.Encounters; +using Strata.DecisionSupport.Models.Jazz; +using Strata.DecisionSupport.Models.Queries; +using Jazz = Strata.DecisionSupport.Models.Jazz; + +namespace Strata.ContinuousImprovement.Client +{ + public partial interface IContinuousImprovementService + { + Task DeleteOpportunityLink(Guid databaseGuid, Guid configurationGuid, Guid opportunityGuid, CancellationToken cancellationToken); + Task DeleteInitiativeLink(Guid databaseGuid, Guid configurationGuid, Guid initiativeGuid, CancellationToken cancellationToken); + Task RolloverOpportunity(RolloverOpportunityInfo rolloverOpportunityInfo, CancellationToken cancellationToken); + Task RolloverOpportunityLink(Guid databaseGuid, + Guid sourceConfigurationGuid, Guid sourceOpportunityGuid, + Guid rolloverConfigurationGuid, Guid rolloverOpportunityGuid, Guid rolloverInitiativeGuid, CancellationToken cancellationToken); + [Obsolete] + Task CreateChargeCodeOpportunityDetails(ChargeCodeDetailInfo detailInfo, CancellationToken cancellationToken); + + Task> DecisionSupportMsdrgEncounterInfo(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default); + Task> DecisionSupportAprdrgEncounterInfo(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default); + Task> DecisionSupportCptCodeEncounterInfo(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default); + Task> DecisionSupportPatientPopulationEncounterInfo(EncounterQueryInfo encounterInfo, CancellationToken cancellationToken = default); + Task> DecisionSupportEncounterCostInfo(EncounterCostQueryInfo encounterCostInfo, CancellationToken cancellationToken = default); + Task> DecisionSupportDepartmentEncounterUnitOfServiceInfo(DepartmentEncounterUnitOfServiceQueryInfo departmentEncounterUnitOfServiceQueryInfo, CancellationToken cancellationToken = default); + Task> DecisionSupportEncounterUnitOfServiceInfo(EncounterUnitOfServiceQueryInfo enounterUnitOfServiceQueryInfo, CancellationToken cancellationToken = default); + Task> DecisionSupportVariationEncounterChargeInfo(VariationEncounterChargeQueryInfo variationEncounterChargeQueryInfo, CancellationToken cancellationToken = default); + Task> DecisionSupportEncounterChargeInfo(EncounterChargeQueryInfo encounterChargeQueryInfo, CancellationToken cancellationToken = default); + Task> DecisionSupportLOSOpportunityInfo(LOSOpportunityQueryInfo losOpportunityInfoqueryInfo, CancellationToken cancellationToken = default); + Task> ExplorationEncounterInfo(long explorationId, Guid configurationGuid, CancellationToken cancellationToken = default); + Task GetExplorationPopulationById(long explorationId, CancellationToken cancellationToken = default); + Task> GetAllWorkbooks(CancellationToken cancellationToken = default); + Task> GetOpportunityWorkbooks(Guid opportunityGuid, CancellationToken cancellationToken = default); + Task> GetOpportunityTypeWorkbooks(int opportunityType, CancellationToken cancellationToken = default); + Task> CreateOpportunityWorkbooks(IEnumerable opportunityWorkbooks, CancellationToken cancellationToken = default); + Task> CreateOpportunityTypeWorkbooks(IEnumerable opportunityTypeWorkbooks, CancellationToken cancellationToken = default); + Task DeleteOpportunityWorkbook(Guid opportunityGuid, int workbookId, CancellationToken cancellationToken = default); + Task DeleteOpportunityTypeWorkbook(int opportunityType, int workbookId, CancellationToken cancellationToken = default); + Task> ChargeCodeOpportunityMetricCohortInfo(OpportunityMetricCohortOptions options, CancellationToken cancellationToken); + Task> EncounterOpportunityMetricCohortInfo(OpportunityMetricCohortOptions options, CancellationToken cancellationToken); + Task RefreshChargeCodeOpportunityBaselineDetailsAsync(int strataId, Guid opportunityGuid, CancellationToken cancellationToken = default); + Task RefreshChargeCodeOpportunityTrackingDetailsAsync(int strataId, Guid opportunityGuid, CancellationToken cancellationToken = default); + Task GetBackgroundClientJobStatus(string jobId, CancellationToken cancellationToken = default); + Task PopulateQviMetrics(Guid configurationGuid, bool isBaseline, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Client/IOpportunityMetricBase.cs b/src/Strata.ContinuousImprovement.Client/IOpportunityMetricBase.cs new file mode 100644 index 0000000..26c6ac0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/IOpportunityMetricBase.cs @@ -0,0 +1,26 @@ +namespace Strata.ContinuousImprovement.Client +{ + public interface IOpportunityMetricBase + { + int EncounterGroup { get; } + IEnumerable CaseTypeIds { get; } + IEnumerable PatientPopulationIds { get; } + IEnumerable ChargeCodeIds { get; } + string OpportunityLevel { get; set; } + bool IsBaseline { get; set; } + IEnumerable OpportunityLevelIds { get; } + string OpportunityLevelIdAsCSV { get; } + OpportunityLevel OpportunityLevelEnum { get; } + + } + + public enum OpportunityLevel + { + Organization, + Entity, + ServiceLine, + Specialty, + Provider + } + +} diff --git a/src/Strata.ContinuousImprovement.Client/IQualityVariationService.cs b/src/Strata.ContinuousImprovement.Client/IQualityVariationService.cs new file mode 100644 index 0000000..7250a26 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/IQualityVariationService.cs @@ -0,0 +1,14 @@ +using Strata.ContinuousImprovement.Client.Models.Qvi; + +namespace Strata.ContinuousImprovement.Client +{ + public partial interface IContinuousImprovementService + { + Task> GetNonQviCaseTypeFamilies(Guid configurationGuid, bool isBaseline = false, CancellationToken cancellationToken = default); + Task> GetNonQviDrgTypes(Guid configurationGuid, bool isBaseline = false, CancellationToken cancellationToken = default); + Task> GetQviLineItemCaseTypeFamilies(Guid configurationGuid, bool isBaseline = false, CancellationToken cancellationToken = default); + Task> GetQviLineItemDrgTypes(Guid configurationGuid, bool isBaseline = false, CancellationToken cancellationToken = default); + Task> GetQviLineItemEncounterSavings(Guid configurationGuid, Guid? opportunityGuid = null, bool? isBaseline = null, CancellationToken cancellationToken = default); + Task> GetQviEncounters(Guid configurationGuid, DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); + } +} diff --git a/src/Strata.ContinuousImprovement.Client/IUtilizationVarianceService.cs b/src/Strata.ContinuousImprovement.Client/IUtilizationVarianceService.cs new file mode 100644 index 0000000..af84bcc --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/IUtilizationVarianceService.cs @@ -0,0 +1,11 @@ +using Strata.DecisionSupport.Models.Jazz; +using Strata.DecisionSupport.Models.Queries; + +namespace Strata.ContinuousImprovement.Client +{ + public partial interface IContinuousImprovementService + { + Task> AllChargeInfo(AllChargeQueryInfo allChargeQueryInfo, CancellationToken cancellationToken = default); + Task> ChargeInfo(ChargeQueryInfo chargeQueryInfo, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Client/Models/Qvi/NonQviCaseTypeFamilyInfo.cs b/src/Strata.ContinuousImprovement.Client/Models/Qvi/NonQviCaseTypeFamilyInfo.cs new file mode 100644 index 0000000..316e9c4 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/Models/Qvi/NonQviCaseTypeFamilyInfo.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Client.Models.Qvi +{ + public class NonQviCaseTypeFamilyInfo + { + public int CaseTypeFamilyId { get; set; } + public decimal MedianVariableDirectCostPerCase { get; set; } + public int CaseCount { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/Models/Qvi/NonQviDrgTypeInfo.cs b/src/Strata.ContinuousImprovement.Client/Models/Qvi/NonQviDrgTypeInfo.cs new file mode 100644 index 0000000..1101e8c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/Models/Qvi/NonQviDrgTypeInfo.cs @@ -0,0 +1,9 @@ +namespace Strata.ContinuousImprovement.Client.Models.Qvi +{ + public class NonQviDrgTypeInfo + { + public string DrgType { get; set; } + public decimal MedianVariableDirectCostPerCase { get; set; } + public int CaseCount { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviEncounterInfo.cs b/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviEncounterInfo.cs new file mode 100644 index 0000000..afe185d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviEncounterInfo.cs @@ -0,0 +1,19 @@ +namespace Strata.ContinuousImprovement.Client.Models.Qvi +{ + public class QviEncounterInfo + { + public long EncounterId { get; set; } + public string EncounterRecordNumber { get; set; } + public string DrgType { get; set; } + public decimal VariableDirectCost { get; set; } + public int LengthOfStay { get; set; } + public decimal NetRevenue { get; set; } + public int CaseTypeFamilyId { get; set; } + public int EntityId { get; set; } + public string Entity { get; set; } + public int PhysicianSpecialtyId { get; set; } + public string PhysicianSpecialty { get; set; } + public int ServiceLineId { get; set; } + public string ServiceLine { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviLineItemCaseTypeFamilyInfo.cs b/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviLineItemCaseTypeFamilyInfo.cs new file mode 100644 index 0000000..96037c1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviLineItemCaseTypeFamilyInfo.cs @@ -0,0 +1,10 @@ +namespace Strata.ContinuousImprovement.Client.Models.Qvi +{ + public class QviLineItemCaseTypeFamilyInfo + { + public int QualityVariationEventId { get; set; } + public int CaseTypeFamilyId { get; set; } + public decimal MedianVariableDirectCostPerCase { get; set; } + public int CaseCount { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviLineItemDrgTypeInfo.cs b/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviLineItemDrgTypeInfo.cs new file mode 100644 index 0000000..9882f25 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviLineItemDrgTypeInfo.cs @@ -0,0 +1,10 @@ +namespace Strata.ContinuousImprovement.Client.Models.Qvi +{ + public class QviLineItemDrgTypeInfo + { + public int QualityVariationEventId { get; set; } + public string DrgType { get; set; } + public decimal MedianVariableDirectCostPerCase { get; set; } + public int CaseCount { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviLineItemEncounterSavingInfo.cs b/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviLineItemEncounterSavingInfo.cs new file mode 100644 index 0000000..e5ac5bd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/Models/Qvi/QviLineItemEncounterSavingInfo.cs @@ -0,0 +1,30 @@ +namespace Strata.ContinuousImprovement.Client.Models.Qvi +{ + public class QviLineItemEncounterSavingInfo + { + public string EncounterRecordNumber { get; set; } + public decimal Savings { get; set; } + public bool IsBaseline { get; set; } + public int PhysicianId { get; set; } + public string PhysicianName { get; set; } + public int PhysicianSpecialtyId { get; set; } + public string PhysicianSpecialty { get; set; } + public int EntityId { get; set; } + public string Entity { get; set; } + public int FinancialClassId { get; set; } + public int PayorId { get; set; } + public int ContractGroupingId { get; set; } + public int PayorGroupId { get; set; } + public int InsurancePlanId { get; set; } + public decimal NetRevenue { get; set; } + public int LengthOfStay { get; set; } + public decimal PesVariableDirectCost { get; set; } + public decimal TotalCost { get; set; } + public DateTime DischargeDateTime { get; set; } + public int CaseTypeFamilyId { get; set; } + public decimal VariableDirectCost { get; set; } + public string ServiceLine { get; set; } + public int ServiceLineId { get; set; } + public string CaseTypeFamily { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/OpportunityMetricBase.cs b/src/Strata.ContinuousImprovement.Client/OpportunityMetricBase.cs new file mode 100644 index 0000000..f4ecdd5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/OpportunityMetricBase.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Client +{ + public class OpportunityMetricBase : IOpportunityMetricBase + { + public int EncounterGroup { get; set; } + + public IEnumerable CaseTypeIds { get; set; } + + public IEnumerable PatientPopulationIds { get; set; } + + public IEnumerable ChargeCodeIds { get; set; } + + public string OpportunityLevel { get; set; } + public bool IsBaseline { get; set; } + + public IEnumerable OpportunityLevelIds { get; set; } + + public string OpportunityLevelIdAsCSV { get; set; } + + public OpportunityLevel OpportunityLevelEnum { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/OpportunityMetricCohortInfo.cs b/src/Strata.ContinuousImprovement.Client/OpportunityMetricCohortInfo.cs new file mode 100644 index 0000000..58cbd2d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/OpportunityMetricCohortInfo.cs @@ -0,0 +1,16 @@ +namespace Strata.ContinuousImprovement.Client +{ + public class OpportunityMetricCohortInfo + { + public int LevelID { get; set; } + public string Name { get; set; } + public int Month { get; set; } + public int Volume { get; set; } + public decimal Revenue { get; set; } + public decimal Cost { get; set; } + public decimal Margin { get; set; } + public decimal RevenuePerCase { get; set; } + public decimal CostPerCase { get; set; } + public bool IsBaseline { get; set; } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/OpportunityMetricCohortOptions.cs b/src/Strata.ContinuousImprovement.Client/OpportunityMetricCohortOptions.cs new file mode 100644 index 0000000..55e356f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/OpportunityMetricCohortOptions.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Strata.ContinuousImprovement.Client +{ + public class OpportunityMetricCohortOptions + { + public Guid ConfigurationGuid { get; set; } + public OpportunityMetricBase Data { get; set; } + public string InnerJoin { get; set; } = string.Empty; + } +} diff --git a/src/Strata.ContinuousImprovement.Client/QualityVariationService.cs b/src/Strata.ContinuousImprovement.Client/QualityVariationService.cs new file mode 100644 index 0000000..b1d770e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/QualityVariationService.cs @@ -0,0 +1,47 @@ +using Strata.ApiCommunication.Http; +using Strata.ContinuousImprovement.Client.Models.Qvi; + +namespace Strata.ContinuousImprovement.Client +{ + public partial class ContinuousImprovementService : IContinuousImprovementService + { + public async Task> GetNonQviCaseTypeFamilies(Guid configurationGuid, bool isBaseline = false, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/quality-variation-savings/non-qvi/case-type-families?configurationGuid={configurationGuid}&isBaseline={isBaseline}"); + return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> GetNonQviDrgTypes(Guid configurationGuid, bool isBaseline = false, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/quality-variation-savings/non-qvi/drg-types?configurationGuid={configurationGuid}&isBaseline={isBaseline}"); + return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> GetQviLineItemCaseTypeFamilies(Guid configurationGuid, bool isBaseline = false, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/quality-variation-savings/line-items/case-type-families?configurationGuid={configurationGuid}&isBaseline={isBaseline}"); + return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> GetQviLineItemDrgTypes(Guid configurationGuid, bool isBaseline = false, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/quality-variation-savings/line-items/drg-types?configurationGuid={configurationGuid}&isBaseline={isBaseline}"); + return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> GetQviLineItemEncounterSavings(Guid configurationGuid, Guid? opportunityGuid = null, bool? isBaseline = null, CancellationToken cancellationToken = default) + { + var url = $"api/v1/quality-variation-savings/line-items/encounter-savings?configurationGuid={configurationGuid}"; + if (opportunityGuid.HasValue) url += $"&opportunityGuid={opportunityGuid.Value}"; + if (isBaseline.HasValue) url += $"&isBaseline={isBaseline.Value}"; + var request = new HttpRequestMessage(HttpMethod.Get, url); + return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> GetQviEncounters(Guid configurationGuid, DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"api/v1/quality-variation-encounters?configurationGuid={configurationGuid}&startDate={startDate:yyyy-MM-dd}&endDate={endDate:yyyy-MM-dd}"); + return await _httpClient.SendAsync>(request, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/RolloverOpportunityInfo.cs b/src/Strata.ContinuousImprovement.Client/RolloverOpportunityInfo.cs new file mode 100644 index 0000000..992480b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/RolloverOpportunityInfo.cs @@ -0,0 +1,24 @@ +namespace Strata.ContinuousImprovement.Client +{ + public class RolloverOpportunityInfo + { + public Guid? DatabaseGuid { get; set; } + public Guid SourceConfigurationGuid { get; set; } + public Guid SourceOpportunityGuid { get; set; } + public Guid RolloverConfigurationGuid { get; set; } + public Guid RolloverOpportunityGuid { get; set; } + public Guid RolloverInitiativeGuid { get; set; } + + public RolloverOpportunityInfo() { } + + public RolloverOpportunityInfo(Guid? databaseGuid, Guid sourceConfigurationGuid, Guid sourceOpportunityGuid, Guid rolloverConfigurationGuid, Guid rolloverOpportunityGuid, Guid rolloverInitiativeGuid) + { + DatabaseGuid = databaseGuid; + SourceConfigurationGuid = sourceConfigurationGuid; + SourceOpportunityGuid = sourceOpportunityGuid; + RolloverConfigurationGuid = rolloverConfigurationGuid; + RolloverOpportunityGuid = rolloverOpportunityGuid; + RolloverInitiativeGuid = rolloverInitiativeGuid; + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Client/Strata.ContinuousImprovement.Client.csproj b/src/Strata.ContinuousImprovement.Client/Strata.ContinuousImprovement.Client.csproj new file mode 100644 index 0000000..f3a6189 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/Strata.ContinuousImprovement.Client.csproj @@ -0,0 +1,23 @@ + + + + net8.0;netstandard2.0 + enable + enable + 10.0 + + + + + + + + + + + + + + + + diff --git a/src/Strata.ContinuousImprovement.Client/UtilizationVariationService.cs b/src/Strata.ContinuousImprovement.Client/UtilizationVariationService.cs new file mode 100644 index 0000000..bb97dd9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/UtilizationVariationService.cs @@ -0,0 +1,23 @@ +using Strata.ApiCommunication.Http; +using Strata.DecisionSupport.Models.Jazz; +using Strata.DecisionSupport.Models.Queries; + +namespace Strata.ContinuousImprovement.Client +{ + public partial class ContinuousImprovementService : IContinuousImprovementService + { + public async Task> AllChargeInfo(AllChargeQueryInfo allChargeQueryInfo, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/utilization-variation/all-charge-info"); + await request.SetContentAsJsonAsync(allChargeQueryInfo, cancellationToken); + return await _httpClient.SendAsync>(request, cancellationToken); + } + + public async Task> ChargeInfo(ChargeQueryInfo chargeQueryInfo, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/utilization-variation/charge-info"); + await request.SetContentAsJsonAsync(chargeQueryInfo, cancellationToken); + return await _httpClient.SendAsync>(request, cancellationToken); + } + } +} diff --git a/src/Strata.ContinuousImprovement.Client/runtimeconfig.template.json b/src/Strata.ContinuousImprovement.Client/runtimeconfig.template.json new file mode 100644 index 0000000..9cbaf60 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Client/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.Drawing.EnableUnixSupport": true + } +} diff --git a/src/Strata.ContinuousImprovement.Dashboard/Program.cs b/src/Strata.ContinuousImprovement.Dashboard/Program.cs new file mode 100644 index 0000000..68e0999 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Dashboard/Program.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Strata.ContinuousImprovement.Biz.Configurations; + +namespace Strata.ContinuousImprovement.Dashboard +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services.AddStrataHangfire() + .AddContinuousImprovementServices(hostContext.Configuration); + }) + .UseStartup(); + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Dashboard/Properties/launchSettings.json b/src/Strata.ContinuousImprovement.Dashboard/Properties/launchSettings.json new file mode 100644 index 0000000..7b0105d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Dashboard/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:49534", + "sslPort": 44369 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "Hangfire__RunLocally": "true", + "AWS_S3_US_EAST_1_REGIONAL_ENDPOINT": "regional", + "AWS_Profile": "sdt-continuous-improvement-service-role", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Strata.ContinuousImprovement.Dashboard": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Dashboard/Startup.cs b/src/Strata.ContinuousImprovement.Dashboard/Startup.cs new file mode 100644 index 0000000..4255fbb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Dashboard/Startup.cs @@ -0,0 +1,37 @@ +using Hangfire; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Strata.Hangfire.AspNetCore.Filters.Dashboard; + +namespace Strata.ContinuousImprovement.Dashboard +{ + public class Startup + { + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + // ReSharper disable once UnusedMember.Global + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + var pathMatch = "/dashboard"; + +#if DEBUG + pathMatch = string.Empty; +#endif + + app.UseHangfireDashboard(pathMatch, new DashboardOptions + { + Authorization = new[] { new AllowAnyoneDashboardAuthorizationFilter() } + }); + + app.UseRouting(); + } + } + + +} + diff --git a/src/Strata.ContinuousImprovement.Dashboard/Strata.ContinuousImprovement.Dashboard.csproj b/src/Strata.ContinuousImprovement.Dashboard/Strata.ContinuousImprovement.Dashboard.csproj new file mode 100644 index 0000000..b1eac17 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Dashboard/Strata.ContinuousImprovement.Dashboard.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + Strata Decision Technology + Copyright © Strata Decision Technology 2023 + Hangfire dashboard for Continuous Improvement Service + + + + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + diff --git a/src/Strata.ContinuousImprovement.Dashboard/appsettings.Development.json b/src/Strata.ContinuousImprovement.Dashboard/appsettings.Development.json new file mode 100644 index 0000000..b02fb38 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Dashboard/appsettings.Development.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + + "configuration": { + "basicAuthUsername": "BkQsRDdmdjkFNhKVuTI.TpujQJXuKhGDcV.F_hjADMRTaEUWMJnviaesoOrhhfnz", + "basicAuthPassword": "GWPIIWECDqh.hjDNdpxVEfeFe-bYFwPSx-oBGFD_od_SUQVk-QqTIkZY_NXMeqeo" + }, + + "StrataConfigServerBaseUrl": "https://configuration.dev.stratanetwork.net" +} diff --git a/src/Strata.ContinuousImprovement.Dashboard/appsettings.json b/src/Strata.ContinuousImprovement.Dashboard/appsettings.json new file mode 100644 index 0000000..733f852 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Dashboard/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + + "Snowflake": { + "Url": "https://stratadev.us-east-1.privatelink.snowflakecomputing.com", + "Account": "stratadev" + }, + + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Dashboard/runtimeconfig.template.json b/src/Strata.ContinuousImprovement.Dashboard/runtimeconfig.template.json new file mode 100644 index 0000000..9cbaf60 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Dashboard/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.Drawing.EnableUnixSupport": true + } +} diff --git a/src/Strata.ContinuousImprovement.Service/Program.cs b/src/Strata.ContinuousImprovement.Service/Program.cs new file mode 100644 index 0000000..1df5078 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Service/Program.cs @@ -0,0 +1,62 @@ +using Hangfire; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Strata.Configuration.Client.DependencyInjection; +using Strata.ContinuousImprovement.Biz.Configurations; +using Strata.ContinuousImprovement.Biz.Hubs; +using Strata.Hangfire.Configuration; +using Strata.Logging.DependencyInjection; +using Strata.SignalR.Configuration; +using System; +using System.Threading.Tasks; + +namespace Strata.ContinuousImprovement.Biz.AutoMapper +{ + public class Program + { + public static async Task Main(string[] args) + { + var host = Host.CreateDefaultBuilder() + .ConfigureLogging(x => x.AddConsole().SetMinimumLevel(LogLevel.Information)) + .UseWindowsService() + .UseStrataConfiguration() + .UseStrataLogging() + .ConfigureServices((hostContext, services) => + { + services.Configure(option => { option.ShutdownTimeout = TimeSpan.FromSeconds(60); }) + .AddStrataHangfire() + .AddStrataSignalR() + .AddContinuousImprovementServices(hostContext.Configuration) + + .AddHangfireServer(options => + { + options.StopTimeout = TimeSpan.FromSeconds(15); + options.ShutdownTimeout = TimeSpan.FromSeconds(30); + options.ServerTimeout = TimeSpan.FromMinutes(5); + options.Queues = new[] { "default" }; + + }) + .AddHangfireServer(options => + { + options.WorkerCount = 25; + options.StopTimeout = TimeSpan.FromSeconds(15); + options.ShutdownTimeout = TimeSpan.FromSeconds(30); + options.ServerTimeout = TimeSpan.FromMinutes(5); + options.Queues = new[] { "continuousimprovement-standards" }; + + }) + .AddHostedService(); + + services.Configure(options => + { + options.Configure((provider, configuration) => configuration.UseFilter(provider)); + }); + + }) + .Build(); + + await host.RunAsync(); + } + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Service/Properties/launchSettings.json b/src/Strata.ContinuousImprovement.Service/Properties/launchSettings.json new file mode 100644 index 0000000..1d99bfe --- /dev/null +++ b/src/Strata.ContinuousImprovement.Service/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "Strata.ContinuousImprovement.Service": { + "commandName": "Project", + "environmentVariables": { + "Hangfire__RunLocally": "true", + "DOTNET_ENVIRONMENT": "Development", + "AWS_S3_US_EAST_1_REGIONAL_ENDPOINT": "regional", + "AWS_Profile": "sdt-continuous-improvement-service-role" + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Service/Strata.ContinuousImprovement.Service.csproj b/src/Strata.ContinuousImprovement.Service/Strata.ContinuousImprovement.Service.csproj new file mode 100644 index 0000000..37086b3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Service/Strata.ContinuousImprovement.Service.csproj @@ -0,0 +1,35 @@ + + + + net8.0 + dotnet-Strata.Replication.Service-3BECFA0B-EDE2-4517-AB7C-6544CAFB731B + Strata Decision Technology + Strata Decision Technology + Continuous Improvement Service + Copyright © Strata Decision Technology 2023 + + + + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/src/Strata.ContinuousImprovement.Service/appsettings.Development.json b/src/Strata.ContinuousImprovement.Service/appsettings.Development.json new file mode 100644 index 0000000..f50f253 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Service/appsettings.Development.json @@ -0,0 +1,47 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + + "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_" + }, + "gmdnFTP": { + "ftpHostUrl": "ftp.gmdnagency.org", + "termUser": { + "userName": "user49", + "password": "6sdt!@yUyfXpamX5" + }, + "categoryUser": { + "userName": "userC1030", + "password": "TtTmiH96yzbMqh" + } + }, + "databases": { + "databaseRegex": "^D[\\d]{4}-sql-[\\d]{2}\\.sdt\\.local$" + }, + "QueryRecursion": { + "CCOpportunityDetailQuery": { + "Start": 1, + "End": 30, + "Increment": 5 + } + }, + "MailGun": { + "ApiKey": "", + "BaseUrl": "https://api.mailgun.net/v3/", + "Domain": "notifications-dev.stratanetwork.com", + "FromEmailAddress": "noreply@notifications-dev.stratanetwork.com" + } +} diff --git a/src/Strata.ContinuousImprovement.Service/appsettings.QA.json b/src/Strata.ContinuousImprovement.Service/appsettings.QA.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Service/appsettings.QA.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/Strata.ContinuousImprovement.Service/appsettings.json b/src/Strata.ContinuousImprovement.Service/appsettings.json new file mode 100644 index 0000000..456e746 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Service/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "endpoints": { + "dataschema": "https://dataschema-api.dev.stratanetwork.net/", + "schema": "https://schema-api.dev.stratanetwork.net", + "analytics": "https://analytics-api.dev.stratanetwork.net" + }, + "Snowflake": { + "Url": "https://stratadev.us-east-1.privatelink.snowflakecomputing.com", + "Account": "stratadev" + } +} diff --git a/src/Strata.ContinuousImprovement.Service/runtimeconfig.template.json b/src/Strata.ContinuousImprovement.Service/runtimeconfig.template.json new file mode 100644 index 0000000..9cbaf60 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Service/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.Drawing.EnableUnixSupport": true + } +} diff --git a/src/Strata.ContinuousImprovement.Web/Strata.ContinuousImprovement.Web.csproj b/src/Strata.ContinuousImprovement.Web/Strata.ContinuousImprovement.Web.csproj new file mode 100644 index 0000000..0f9305c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/Strata.ContinuousImprovement.Web.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + + + + + + + + + diff --git a/src/Strata.ContinuousImprovement.Web/Strata.ContinuousImprovement.Web.sln b/src/Strata.ContinuousImprovement.Web/Strata.ContinuousImprovement.Web.sln new file mode 100644 index 0000000..fe987ce --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/Strata.ContinuousImprovement.Web.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Strata.ContinuousImprovement.Web", "Strata.ContinuousImprovement.Web.csproj", "{14AB6776-4DB2-2C3E-B6C8-4649A72F6DA4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {14AB6776-4DB2-2C3E-B6C8-4649A72F6DA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14AB6776-4DB2-2C3E-B6C8-4649A72F6DA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14AB6776-4DB2-2C3E-B6C8-4649A72F6DA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14AB6776-4DB2-2C3E-B6C8-4649A72F6DA4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0C56B17D-050B-455A-BC2B-83D595DFC0BE} + EndGlobalSection +EndGlobal diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/README.md b/src/Strata.ContinuousImprovement.Web/continuousimprovement/README.md new file mode 100644 index 0000000..6f17918 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/README.md @@ -0,0 +1,67 @@ +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Getting Started + +Verify `public/config.js` has the correct values for your application. +Run `npm install --legacy-peer-deps` +Run `npm outdated` to see if you have any outdated dependencies. You should update all dependencies to latest by running `npm update`. +**FOLLOW THE GUIDE HERE FOR ESLINT SETUP:** [https://confluence.sdt.local/pages/viewpage.action?pageId=53429096](https://confluence.sdt.local/pages/viewpage.action?pageId=53429096) + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +## Debugger for Chrome + +Add the `Debugger for Chrome` extension from the Visual Studio Code extension store. +Now, click on the bug icon on the left pane of Visual Studio Code, find the gear icon near the top right corner of the left side pane that is now open, and click "Add New Configuration". + +Choose the `Chrome: Launch` configuration. +In the launch.json file update the port of the url: `"url": "http://localhost:3000"`. +For more information the configuration should match the set up as described in [Configure the Chrome Debugger](https://code.visualstudio.com/docs/nodejs/reactjs-tutorial). +Setting breakpoints should now work in Visual Studio Code with Chrome. + +## React Developer Tools + +Add the `React Developer Tools` extension from the Chrome web store as described [here](https://reactjs.org/blog/2015/09/02/new-react-developer-tools.html#installation) +You will get two new tabs in your Chrome DevTools: `⚛️ Components` and `⚛️ Profiler`. +Now you can select/inspect the components & view/edit the current props and state. diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/docs/exploration-grid-architecture.md b/src/Strata.ContinuousImprovement.Web/continuousimprovement/docs/exploration-grid-architecture.md new file mode 100644 index 0000000..c674bba --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/docs/exploration-grid-architecture.md @@ -0,0 +1,789 @@ +# Exploration Grid Architecture + +## Overview + +This document details the architecture and interaction patterns between the Case Type Family exploration grids (Summary and Detail views) and their supporting React hooks. The architecture emphasizes code reuse, separation of concerns, and maintainability through a custom hook-based approach. + +## Component Overview + +```mermaid +graph TB + subgraph "Grid Components" + Summary[CaseTypeFamilySummaryGrid] + Detail[CaseTypeFamilyDetailGrid] + RefinementDrawer[RefinementOptionsDrawer] + end + + subgraph "Shared Hooks" + ColumnConfigs[useExplorationColumnConfigs] + ColumnMgmt[useColumnManagement] + FilterMgmt[useFilterManagement] + ScrollMgmt[useScrollManagement] + EncounterMgmt[useEncounterManagement] + DataFetch[useExplorationDataFetching] + BannerHook[useBenchmarkingBanner] + DrawerHook[useRefinementDrawer] + end + + Summary --> ColumnConfigs + Summary --> ColumnMgmt + Summary --> FilterMgmt + Summary --> ScrollMgmt + Summary --> BannerHook + Summary --> DataFetch + + Detail --> ColumnConfigs + Detail --> ColumnMgmt + Detail --> FilterMgmt + Detail --> EncounterMgmt + Detail --> BannerHook + Detail --> DataFetch + + RefinementDrawer --> DrawerHook + RefinementDrawer --> BannerHook + + ColumnMgmt --> ColumnConfigs + + style ColumnConfigs fill:#e1f5ff + style ColumnMgmt fill:#e1f5ff + style FilterMgmt fill:#fff4e1 + style BannerHook fill:#e1f5ff + style DrawerHook fill:#e1f5ff + style Summary fill:#f0f0f0 + style Detail fill:#f0f0f0 + style RefinementDrawer fill:#f0f0f0 +``` + +## Hook Dependencies and Data Flow + +### 1. useExplorationColumnConfigs Hook + +**Purpose**: Provides unified column configuration logic for both Summary and Detail grids. + +**Key Features**: +- Single source of truth for column definitions +- View-specific customizations (detail vs summary) +- Conditional column visibility +- Dynamic body function assignment +- Includes column rendering helpers (getNameColumnBody, getMortalityRate) +- All column-related logic in one location + +```mermaid +flowchart LR + subgraph "Input" + ViewType[viewType: 'summary' | 'detail'] + DetailConfig[detailConfig?: IDetailViewConfig] + end + + subgraph "useExplorationColumnConfigs" + GetConfigs[getColumnConfigs Function] + + subgraph "Column Groups" + Name[Name Column] + Detail_CB[Detail Checkbox] + Population[Entire Population] + Benchmark[Benchmark Data] + Refined[Refined Population] + Excluded[Excluded Population] + end + end + + subgraph "Output" + Configs[ITreeGridColumnConfig[]] + end + + ViewType --> GetConfigs + DetailConfig --> GetConfigs + + GetConfigs --> Name + GetConfigs --> Detail_CB + GetConfigs --> Population + GetConfigs --> Benchmark + GetConfigs --> Refined + GetConfigs --> Excluded + + Name --> Configs + Detail_CB --> Configs + Population --> Configs + Benchmark --> Configs + Refined --> Configs + Excluded --> Configs +``` + +**Column Differences by View**: + +| Column Group | Detail View | Summary View | +|-------------|-------------|--------------| +| Name | Custom body with navigation links | Simple body with navigation | +| isExcluded (Checkbox) | ✓ Visible | ✗ Hidden | +| medianGrossCharges | ✓ Visible | ✗ Hidden | +| Column Widths | Auto | Explicit (86px) | +| Body Functions | Custom for parent/child rows | Custom for leaf/non-leaf rows | + +### 2. useFilterManagement Hook + +**Purpose**: Centralized filter state management with session persistence. + +```mermaid +sequenceDiagram + participant Component as Grid Component + participant Hook as useFilterManagement + participant Session as Session Storage + participant API as Exploration API + + Component->>Hook: Initialize with config + Hook->>Session: Load saved filters + Session-->>Hook: Return filters (if any) + Hook-->>Component: Return filter state & handlers + + Component->>Hook: User modifies filters + Hook->>Session: Persist updated filters + Hook->>Component: Trigger renderFilterBar() + + Note over Component,API: Filter changes trigger data fetch + Component->>API: Fetch with new filters +``` + +**Provides**: +- `filters` - Current filter chip items +- `exclusionCriteria` - Exclusion rules +- `peerGroupFilters` - Peer group selection +- `renderFilterBar()` - Pre-configured FilterBar component +- `handleDrawerApply()` - Filter drawer handler + +### 3. useColumnManagement Hook + +**Purpose**: Manages column visibility, ordering, and user preferences. + +```mermaid +flowchart TD + subgraph "Input" + GridName[gridName: string] + AddlGrids[additionalGridNames?: string[]] + GetConfigs[getColumnConfigs: Function] + ShowBenchmark[showPeerGroupBenchmarking: boolean] + end + + subgraph "useColumnManagement" + LoadPrefs[Load Column Preferences] + ApplyVisibility[Apply Visibility Rules] + SyncGrids[Sync Across Grids] + + LoadPrefs --> ApplyVisibility + ApplyVisibility --> SyncGrids + end + + subgraph "Output" + GetWithVis[getColumnsWithVisibility] + RenderPanel[renderColumnManagerPanel] + end + + GridName --> LoadPrefs + AddlGrids --> SyncGrids + GetConfigs --> ApplyVisibility + ShowBenchmark --> ApplyVisibility + + SyncGrids --> GetWithVis + SyncGrids --> RenderPanel +``` + +**Grid Synchronization**: +- `FlexibleExplorationSummary` ↔ `FlexibleExplorationDetails` +- Column preferences are shared between both grids +- Changes in one grid reflect in the other + +### 4. useScrollManagement Hook + +**Purpose**: Manages programmatic scrolling to specific rows in the TreeGrid (Summary grid only). + +**Use Case**: When user clicks on heatmap cell, scroll to and expand the corresponding TreeGrid row. + +```mermaid +sequenceDiagram + participant User + participant Heatmap as BenchmarkingHeatMap + participant Summary as SummaryGrid + participant Hook as useScrollManagement + participant TreeGrid + + User->>Heatmap: Click cell + Heatmap->>Summary: handleHeatMapCellClick(rowName, costDriver) + Summary->>Summary: Find matching row + + alt Row needs expansion + Summary->>Summary: Load children (if needed) + Summary->>Summary: Update expandedRowKeys + end + + Summary->>Hook: scrollToRow({ containerRef, rowKey, dataArray }) + Hook->>TreeGrid: Calculate position & scroll + TreeGrid-->>User: Row visible & highlighted +``` + +### 5. useEncounterManagement Hook + +**Purpose**: Manages encounter inclusion/exclusion logic specific to the Detail grid. +**Location**: `src/exploration/hooks/useEncounterManagement.ts` + +**State Management**: +- Uses multiple `useState` hooks for state arrays with functional updates to prevent stale closures +- `encounterExclusions` - Pending manual exclusions +- `encounterInclusions` - Pending manual inclusions +- `gridEncounterExclusions` - Initial loaded state (immutable reference) +- `encounterExclusionsApplied/InclusionsApplied` - Applied changes +- Applied vs pending changes tracking via `useEffect` with XOR comparison + +**Key Functions**: +- `handleSetEncounterExclusion()` - Toggle individual encounter (uses functional state updates) +- `handleApplyExclusions()` - Apply pending changes +- `handleCancelApplyExclusions()` - Cancel pending changes +- `clearEncounterExclusionsInclusionsStates()` - Reset all exclusions +- `setInitialEncounterExclusions()` - Initialize from grid data +- `isEncounterIncluded()` - Check encounter status + +```mermaid +stateDiagram-v2 + [*] --> Loaded: Load encounter data + + Loaded --> Pending: User toggles checkbox + Pending --> Pending: More toggles + Pending --> Applied: User clicks Apply + Pending --> Loaded: User clicks Cancel + + Applied --> Pending: User toggles checkbox + Applied --> [*]: Data refresh + + note right of Loaded + Grid shows initial + exclusion state + end note + + note right of Pending + Changes tracked in + encounterExclusions/Inclusions + arrays (not yet applied) + end note + + note right of Applied + Applied to + encounterExclusionsApplied + encounterInclusionsApplied + Triggers data refetch + end note +``` + +### 6. useExplorationDataFetching Hook + +**Purpose**: Centralizes data fetching logic for the Detail grid. +**Location**: `src/exploration/hooks/useExplorationDataFetching.ts` + +**Key Features**: +- Manages loading states (loading, firstLoading) +- Fetches encounter group details and metrics +- Handles filter and exclusion criteria changes +- Integrates with population data +- Processes and transforms API responses +- **Returns fresh data** from fetchData to avoid stale state issues + +**Provides**: +- `encounterGroupDetails` - Tree grid data (state) +- `metrics` - Case type family metrics +- `loading` / `firstLoading` - Loading states +- `fetchData()` - Manual data fetch with parameters, **returns Promise** +- `useDataFetching()` - Automatic data fetching effect + +**Important**: The `fetchData` function returns the freshly fetched data in addition to updating state. This prevents race conditions where post-fetch operations might use stale state values before React's state updates complete. + +### 7. useBenchmarkingBanner Hook + +**Purpose**: Manages benchmarking banner visibility state across components. +**Location**: `src/exploration/hooks/useBenchmarkingBanner.ts` + +**Key Features**: +- Session-scoped banner dismissal state +- Conditional visibility based on benchmarking status and optional peer group selection +- Used by Summary Grid, Detail Grid, and RefinementOptionsDrawer +- **Flexible peerGroupId parameter**: optional to support contexts without peer group selection + +**Banner Display Rules**: +```typescript +// When peerGroupId is provided +showBanner = !dismissed && benchmarking && peerGroupId !== 0 + +// When peerGroupId is undefined (e.g., in RefinementOptionsDrawer) +showBanner = !dismissed && benchmarking +``` + +**Parameter Behavior**: +- `peerGroupId` provided: Banner shows only if benchmarking enabled AND peer group selected +- `peerGroupId` undefined/omitted: Banner shows based solely on benchmarking flag (peer group check bypassed) + +**Benefits**: +- Consistent banner behavior across all exploration components +- Persists user preference in session storage +- Single line of code to add banner to any component +- Explicit API design for contexts with/without peer group selection + +### 8. useRefinementDrawer Hook + +**Purpose**: Extracts all business logic from RefinementOptionsDrawer component. +**Location**: `src/exploration/hooks/useRefinementDrawer.ts` + +**Key Features**: +- Manages filter popover state and handlers +- Manages SPH (Statistical Process Healthcare) criteria popover +- Handles chip operations (add, delete, update, click) +- Manages exclusion criteria (ALOS, TDVC) +- Synchronizes population filters to benchmarking criteria +- Provides `getApplyPayload()` for final data preparation + +**State Managed** (~360 lines of logic): +- Filter chips and custom filters +- Active filter selection +- Popover visibility states +- Exclusion criteria (ALOS trim, TDVC trim) +- Date range handling + +**Benefits**: +- Reduced RefinementOptionsDrawer from 431 to 246 lines (43% reduction) +- Presentational component purely focused on UI +- Business logic fully testable in isolation +- Complex chip synchronization logic encapsulated + +## Component Interaction Patterns + +### Summary Grid Flow + +```mermaid +sequenceDiagram + participant User + participant Summary as CaseTypeFamilySummaryGrid + participant FilterHook as useFilterManagement + participant ColumnHook as useExplorationColumnConfigs + participant ColMgmtHook as useColumnManagement + participant API as explorationService + + User->>Summary: Load page + Summary->>FilterHook: Initialize filters + Summary->>ColumnHook: getColumnConfigs(viewType: 'summary') + ColumnHook-->>Summary: Column configs + + Summary->>ColMgmtHook: Initialize with configs + ColMgmtHook-->>Summary: getColumnsWithVisibility() + + Summary->>API: fetchData(filters, exclusionCriteria) + API-->>Summary: encounterGroupSummaries[] + + Summary->>Summary: Render TreeGrid + + User->>Summary: Expand row + Summary->>API: loadCostDriverChildren(id) + API-->>Summary: Updated children + Summary->>Summary: Update state & re-render + + User->>Summary: Click heatmap cell + Summary->>Summary: handleHeatMapCellClick() + Summary->>Summary: Expand row & scroll +``` + +### Detail Grid Flow + +```mermaid +sequenceDiagram + participant User + participant Detail as CaseTypeFamilyDetailGrid + participant FilterHook as useFilterManagement + participant ColumnHook as useExplorationColumnConfigs + participant API as explorationService + + User->>Detail: Load page with caseTypeFamilyId & costDriver + Detail->>FilterHook: Initialize filters (with population data) + Detail->>Detail: Initialize encounter exclusions/inclusions + + Detail->>ColumnHook: getColumnConfigs(viewType: 'detail', detailConfig) + Note over ColumnHook: detailConfig includes:
- isEncounterIncluded
- handleSetEncounterExclusion + ColumnHook-->>Detail: Column configs with checkbox column + + Detail->>API: fetchData(filters, encounterExclusions, encounterInclusions) + API-->>Detail: encounterGroupDetails[] + metrics + + Detail->>Detail: Render TreeGrid with checkboxes + + User->>Detail: Toggle encounter checkbox + Detail->>Detail: handleSetEncounterExclusion(id, isIncluded) + Detail->>Detail: Update encounterExclusions/Inclusions + Detail->>Detail: Show Apply/Cancel action bar + + User->>Detail: Click Apply + Detail->>Detail: setEncounterExclusionsApplied() + Detail->>API: fetchData with new exclusions + API-->>Detail: Updated data +``` + +## Data Flow Architecture + +### Filter to Data Pipeline + +```mermaid +flowchart TD + User[User Input] --> FilterBar[Filter Bar] + FilterBar --> FilterState[Filter State] + + subgraph "useFilterManagement" + FilterState + SessionStorage[(Session Storage)] + FilterState <--> SessionStorage + end + + FilterState --> ExclusionCriteria[Exclusion Criteria] + FilterState --> PeerGroupFilters[Peer Group Filters] + FilterState --> ChipItems[Filter Chip Items] + + ExclusionCriteria --> APICall[API Request] + PeerGroupFilters --> APICall + ChipItems --> APICall + + APICall --> ExplorationService[explorationService] + + subgraph "API Methods" + ExplorationService --> GetSummaries[getCaseTypeFamilySummaries] + ExplorationService --> GetDetails[getCaseTypeFamilyInfo] + ExplorationService --> GetChildren[getCaseTypeFamilyCostDriverInfo] + ExplorationService --> GetHeatMap[getHeatMapSummary] + end + + GetSummaries --> TreeGridData[TreeGrid Data] + GetDetails --> TreeGridData + GetChildren --> TreeGridData + GetHeatMap --> HeatMapData[HeatMap Data] + + TreeGridData --> SummaryGrid[Summary Grid] + TreeGridData --> DetailGrid[Detail Grid] + HeatMapData --> SummaryGrid +``` + +### Column Configuration Pipeline + +```mermaid +flowchart LR + subgraph "Configuration Phase" + ViewType[View Type] --> GetColumnConfigs[useExplorationColumnConfigs] + DetailConfig[Detail Config] --> GetColumnConfigs + GetColumnConfigs --> BaseConfigs[Base Column Configs] + end + + subgraph "Management Phase" + BaseConfigs --> ColManagement[useColumnManagement] + UserPrefs[(User Preferences)] --> ColManagement + ShowBenchmark[Show Benchmarking] --> ColManagement + + ColManagement --> ApplyVisibility[Apply Visibility Rules] + ColManagement --> ApplyOrdering[Apply Column Order] + ApplyVisibility --> ManagedConfigs[Managed Column Configs] + ApplyOrdering --> ManagedConfigs + end + + subgraph "Rendering Phase" + ManagedConfigs --> BuildTreeGrid[buildTreeGrid Helper] + BuildTreeGrid --> Columns[Column Components] + BuildTreeGrid --> HeaderGroup[Header Group] + + Columns --> TreeGrid[TreeGrid Component] + HeaderGroup --> TreeGrid + end +``` + +## State Management Patterns + +### Summary Grid State + +```mermaid +classDiagram + class SummaryGridState { + +loading: boolean + +firstLoading: boolean + +encounterGroupSummaries: IEncounterGroupTreeGridData[] + +sortField: string + +sortOrder: number + +expandedRowKeys: string[] + +heatMapData: IHeatMapData[] + +heatMapLoading: boolean + +manageColumnsVisible: boolean + } + + class FilterState { + +filters: IFilterState + +exclusionCriteria: IExclusionCriteria + +peerGroupFilters: IPeerGroupFilters + } + + class ColumnState { + +columnConfigs: ITreeGridColumnConfig[] + +showPeerGroupBenchmarking: boolean + } + + SummaryGridState --> FilterState : uses + SummaryGridState --> ColumnState : uses +``` + +### Detail Grid State + +```mermaid +classDiagram + class DetailGridState { + +loading: boolean + +firstLoading: boolean + +encounterGroupDetails: IEncounterGroupDetailTreeGridData[] + +metrics: ICaseTypeFamilyMetricDetail + +treeGridKeysData: string[] + +expandedLevel: number + +manageColumnsVisible: boolean + } + + class EncounterState { + +gridEncounterExclusions: number[] + +encounterExclusions: number[] + +encounterInclusions: number[] + +encounterExclusionsApplied: number[] + +encounterInclusionsApplied: number[] + +isApplyExclusionActionBarVisible: boolean + } + + class FilterState { + +filters: IFilterState + +exclusionCriteria: IExclusionCriteria + +peerGroupFilters: IPeerGroupFilters + +explorationPopulationData: IExplorationPopulation + } + + DetailGridState --> EncounterState : uses + DetailGridState --> FilterState : uses +``` + +## Key Design Decisions + +### 1. Shared Column Configuration Hook + +**Problem**: Two grids with 95% identical column definitions but small view-specific differences. + +**Solution**: Created `useExplorationColumnConfigs` hook that: +- Accepts `viewType` parameter ('summary' | 'detail') +- Uses spread operators for conditional properties +- Employs ternary operators for view-specific body functions +- Maintains single source of truth + +**Benefits**: +- Reduced ~370 lines of duplicate code to ~250 lines of shared code +- Easier maintenance - changes apply to both grids +- Type-safe with TypeScript interfaces +- Clear separation of concerns + +### 2. Filter Management Centralization + +**Problem**: Complex filter state with session persistence needed in both grids. + +**Solution**: `useFilterManagement` hook encapsulates: +- Filter state management +- Session storage persistence +- Pre-configured FilterBar component +- Drawer apply handlers + +**Benefits**: +- Consistent filter behavior across grids +- Automatic session persistence +- Reduced boilerplate in components + +### 3. Column Synchronization + +**Problem**: User expects column preferences to sync between Summary and Detail grids. + +**Solution**: `useColumnManagement` with `additionalGridNames`: +- Summary grid references Detail grid: `additionalGridNames: ['FlexibleExplorationDetails']` +- Detail grid references Summary grid: `additionalGridNames: ['FlexibleExplorationSummary']` +- Shared preference storage key + +**Benefits**: +- Seamless user experience +- Single configuration for both grids +- Automatic synchronization + +### 4. Encounter State Management + +**Problem**: Complex state tracking for pending vs applied encounter exclusions/inclusions with potential race conditions. + +**Solution**: Separate state arrays with XOR comparison and functional state updates: +- `gridEncounterExclusions` - Initial loaded state (immutable reference) +- `encounterExclusions/Inclusions` - Pending changes (updated via functional setters) +- `encounterExclusionsApplied/InclusionsApplied` - Applied changes +- All state updates use functional form `setState(prev => ...)` to prevent stale closures + +**Benefits**: +- Clear separation of pending vs applied state +- Ability to cancel pending changes +- Prompts user before navigation with unsaved changes +- **Safe under React batching**: functional updates compose correctly even with rapid toggles +- **No stale closure issues**: always operates on current state + +## Performance Considerations + +### Memoization Strategy + +```mermaid +graph LR + subgraph "Memoized Values" + ColumnConfigs[columnConfigs] + Columns[columns] + HeaderGroup[headerGroup] + ShowPeerBench[showPeerGroupBenchmarking] + end + + subgraph "Dependencies" + GetColumns[getColumnsWithVisibility] + ColumnConfigsInput[columnConfigs] + Benchmarking[benchmarking] + PeerFilters[peerGroupFilters] + end + + GetColumns --> ColumnConfigs + ColumnConfigsInput --> Columns + ColumnConfigsInput --> HeaderGroup + Benchmarking --> ShowPeerBench + PeerFilters --> ShowPeerBench + + style ColumnConfigs fill:#e1f5ff + style Columns fill:#e1f5ff + style HeaderGroup fill:#e1f5ff + style ShowPeerBench fill:#e1f5ff +``` + +**Key Optimizations**: +1. `useMemo` for expensive operations (column building, tree grid construction) +2. `useCallback` for event handlers to prevent unnecessary re-renders +3. Selective dependency arrays in `useEffect` hooks +4. Session storage for filter persistence (reduces API calls) + +## Accessibility Improvements + +### BenchmarkingGridHeatMap Button Refactoring + +The heatmap cells were refactored from clickable `
` elements to Ant Design ` +
+); +``` + +**Example: Conditional Fetching** + +```typescript +const { data, loading, refetch } = useDataFetching({ + configuration: selectedConfig, + filters: activeFilters, + exclusionCriteria: null +}); + +// Data automatically refetches when selectedConfig or activeFilters change +// If configuration or filters are null/undefined, fetch is skipped +``` + +**Benefits** + +- ✅ Automatic refetching when dependencies change +- ✅ Stable function references prevent unnecessary re-renders +- ✅ Built-in loading state management +- ✅ Manual refetch capability for user-triggered updates +- ✅ Guards against fetching with invalid dependencies +- ✅ Consistent loading state handling with finally block + +**Implementation** + +```typescript +const useDataFetching = ({ configuration, filters, exclusionCriteria }) => { + const [data, setData] = useState(); + const [loading, setLoading] = useState(false); + + const fetchData = useCallback(async () => { + if (!configuration || !filters) return; + + setLoading(true); + try { + const result = await api.fetchData(configuration, filters, exclusionCriteria); + setData(result); + } finally { + setLoading(false); + } + }, [configuration, filters, exclusionCriteria]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data, loading, refetch: fetchData }; +}; +``` + +**See Also** +- [React useCallback](https://react.dev/reference/react/useCallback) +- [React useEffect](https://react.dev/reference/react/useEffect) + +--- + +#### Complex State with Reducer + +**Description** + +Manages complex encounter state using useReducer for predictable state updates. This pattern is ideal when state updates depend on previous state or when multiple state properties need to be updated together atomically. The reducer pattern eliminates dependency issues in callbacks since dispatch is stable. + +**Parameters** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `configurationId` | `string` | Configuration identifier for encounters | +| `initialExclusions` | `Array` | Initial exclusion list to populate state | + +**Returns** + +| Property | Type | Description | +|----------|------|-------------| +| `exclusions` | `Array` | Current list of encounter exclusions | +| `handleSetExclusion` | `Function` | Stable callback to update exclusion status | + +**Example: Basic Usage** + +```typescript +const { exclusions, handleSetExclusion } = useEncounterManagement({ + configurationId: '123', + initialExclusions: [] +}); + +// Update exclusion status +handleSetExclusion(encounterId, false); // Exclude encounter +handleSetExclusion(encounterId, true); // Include encounter +``` + +**Example: Reducer Definition** + +```typescript +const encounterReducer = (state, action) => { + switch (action.type) { + case 'SET_EXCLUSION': + return { + ...state, + exclusions: state.exclusions.map(item => + item.id === action.id + ? { ...item, isIncluded: action.isIncluded } + : item + ) + }; + default: + return state; + } +}; +``` + +**Benefits** + +- ✅ No dependency array issues - dispatch is always stable +- ✅ Predictable state updates with pure reducer functions +- ✅ Easier to test - reducer is a pure function +- ✅ Better for complex state with multiple related properties +- ✅ Atomic state updates prevent race conditions + +**Implementation** + +```typescript +const useEncounterManagement = (options) => { + const [state, dispatch] = useReducer(encounterReducer, initialState); + + const handleSetExclusion = useCallback((id: number, isIncluded: boolean) => { + dispatch({ type: 'SET_EXCLUSION', id, isIncluded }); + }, []); // ✅ No dependencies with reducer + + return { + exclusions: state.exclusions, + handleSetExclusion + }; +}; +``` + +**See Also** +- [React useReducer](https://react.dev/reference/react/useReducer) + +### 4. Component Refactoring Checklist + +#### Before Refactoring +- [ ] Identify all useEffect blocks and their dependencies +- [ ] Group related state by domain (filtering, data fetching, UI state) +- [ ] List all functions used in useEffect +- [ ] Note any complex state interactions + +#### During Refactoring +- [ ] Create custom hooks for each domain +- [ ] Wrap all functions in useCallback with correct dependencies +- [ ] Use useReducer for complex state interactions +- [ ] Ensure each useEffect has single responsibility +- [ ] Test each hook independently + +#### After Refactoring +- [ ] No useEffect dependency warnings +- [ ] Each hook has clear, single responsibility +- [ ] Component is simplified and easier to read +- [ ] Functions are stable (wrapped in useCallback) +- [ ] All tests pass + +### 5. Anti-Patterns to Avoid + +#### ❌ Don't suppress dependency warnings +```typescript +useEffect(() => { + fetchData(); +}, [someValue]); // eslint-disable-line react-hooks/exhaustive-deps +``` + +#### ❌ Don't create functions inside useEffect +```typescript +useEffect(() => { + const fetchData = async () => { /* ... */ }; // ❌ Recreated every render + fetchData(); +}, [dependency]); +``` + +#### ❌ Don't mix multiple concerns in one useEffect +```typescript +useEffect(() => { + // ❌ Handling filters, data fetching, and UI state in one effect + if (filtersChanged) { /* filter logic */ } + if (dataChanged) { /* data logic */ } + if (uiChanged) { /* UI logic */ } +}, [filters, data, ui]); +``` + +### 6. Benefits of This Approach + +1. **No dependency warnings**: All dependencies correctly specified +2. **Better testability**: Each hook can be tested in isolation +3. **Reusability**: Hooks can be shared between components +4. **Separation of concerns**: Each hook handles one domain +5. **Easier debugging**: Issues isolated to specific hooks +6. **Better TypeScript support**: Clear input/output types + +### 7. Migration Strategy + +1. **Start with one domain** (e.g., data fetching) +2. **Create custom hook** with proper dependencies +3. **Replace useEffect blocks** one at a time +4. **Test thoroughly** before moving to next domain +5. **Apply same pattern** to related components + +### 8. Claude Skill Integration + +This pattern can be codified into a Claude skill for automatic refactoring: + +```typescript +// Skill: refactor-react-component +// Input: Component with useEffect issues +// Output: Refactored component with custom hooks +// Pattern: Extract state domains → Create custom hooks → Simplify main component +``` + +## Example Commands for Claude + +1. **"Refactor this component's useEffect dependencies"** + - Identify all dependency issues + - Create appropriate custom hooks + - Fix all exhaustive-deps warnings + +2. **"Extract filter management into a custom hook"** + - Create `useFilterManagement` hook + - Move all filter-related state and effects + - Return stable handlers + +3. **"Simplify this component using the reducer pattern"** + - Identify complex state interactions + - Create reducer for state management + - Replace useState with useReducer + +This systematic approach ensures components are maintainable, testable, and follow React best practices. \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/docs/react-state-management-patterns.md b/src/Strata.ContinuousImprovement.Web/continuousimprovement/docs/react-state-management-patterns.md new file mode 100644 index 0000000..1a0c876 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/docs/react-state-management-patterns.md @@ -0,0 +1,174 @@ +# React State Management & useEffect Best Practices + +## Common Problems + +### 1. Exhaustive Dependencies Warnings +```typescript +// ❌ Problem: Function in useEffect but not in dependencies +useEffect(() => { + fetchData(); // Function not in deps +}, [someValue]); // Missing fetchData + +// ❌ Problem: Complex interdependent effects +useEffect(() => { + if (configuration && !explorationPopulationId) { + setOutliersFromSession(); // Function not stable + setSummaryFilters(sessionFilters); // State setter + } +}, [configuration, filters, explorationPopulationId]); // Missing deps +``` + +## Solutions + +### 1. Use useCallback for Stable Function References +```typescript +// ✅ Solution: Wrap functions in useCallback +const fetchData = useCallback(async (criteria: IExclusionCriteria, filters: IFilter) => { + // Implementation +}, [configuration, caseTypeFamilyId, costDriver]); // Include actual dependencies + +const setOutliersFromSession = useCallback(() => { + // Implementation +}, [costOutlierValue, losOutlierValue, exclusionCriteria]); + +useEffect(() => { + fetchData(exclusionCriteria, filters); +}, [fetchData, exclusionCriteria, filters]); // Now fetchData is stable +``` + +### 2. Extract Complex State Logic into Custom Hooks +```typescript +// ✅ Custom hook for filter management +const useFilterManagement = (configuration, explorationPopulationData) => { + const [filters, setFilters] = useState(); + const [summaryFilters, setSummaryFilters] = useState(); + + const initializeFilters = useCallback(() => { + // Consolidate filter initialization logic + }, [configuration, explorationPopulationData]); + + return { filters, summaryFilters, initializeFilters }; +}; +``` + +### 3. Use Reducer for Complex State Interactions +```typescript +// ✅ Reducer for encounter exclusion state +type EncounterState = { + exclusions: number[]; + inclusions: number[]; + exclusionsApplied: number[]; + inclusionsApplied: number[]; +}; + +type EncounterAction = + | { type: 'SET_EXCLUSION'; id: number; isIncluded: boolean } + | { type: 'APPLY_CHANGES' } + | { type: 'RESET' }; + +const encounterReducer = (state: EncounterState, action: EncounterAction): EncounterState => { + switch (action.type) { + case 'SET_EXCLUSION': + // Handle complex exclusion logic + return state; + case 'APPLY_CHANGES': + return { ...state, exclusionsApplied: state.exclusions, inclusionsApplied: state.inclusions }; + default: + return state; + } +}; +``` + +## Recommended Patterns + +### Pattern 1: Single Responsibility Effects +```typescript +// ✅ Each effect has single responsibility +useEffect(() => { + // Only handle configuration initialization + if (configuration && !explorationPopulationId) { + initializeFromConfiguration(); + } +}, [configuration, explorationPopulationId, initializeFromConfiguration]); + +useEffect(() => { + // Only handle exploration population data + if (explorationPopulationData) { + initializeFromPopulationData(); + } +}, [explorationPopulationData, initializeFromPopulationData]); +``` + +### Pattern 2: Stable Function References +```typescript +// ✅ All functions wrapped in useCallback with correct dependencies +const handlePeerGroupFiltering = useCallback((filters: IPeerGroupFilters) => { + setPeerGroupFilters(filters); +}, [setPeerGroupFilters]); // Include setter if needed + +const handleSetEncounterExclusion = useCallback((id: number, isIncluded: boolean) => { + dispatch({ type: 'SET_EXCLUSION', id, isIncluded }); +}, []); // No dependencies if using reducer +``` + +### Pattern 3: Custom Hooks for Related Logic +```typescript +// ✅ Extract related state and effects +const useDataFetching = (configuration, filters, exclusionCriteria) => { + const [loading, setLoading] = useState(false); + const [data, setData] = useState(null); + + const fetchData = useCallback(async () => { + if (!configuration || !filters) return; + + setLoading(true); + try { + const result = await explorationService.getCaseTypeFamilyDetails(/* params */); + setData(result); + } finally { + setLoading(false); + } + }, [configuration, filters, exclusionCriteria]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data, loading, refetch: fetchData }; +}; +``` + +## Anti-Patterns to Avoid + +### ❌ Don't: Ignore exhaustive-deps warnings +```typescript +// eslint-disable-next-line react-hooks/exhaustive-deps +useEffect(() => { + fetchData(); +}, [someValue]); // Don't suppress warnings +``` + +### ❌ Don't: Put everything in one giant useEffect +```typescript +useEffect(() => { + // 50 lines of mixed concerns + if (condition1) { /* logic 1 */ } + if (condition2) { /* logic 2 */ } + if (condition3) { /* logic 3 */ } +}, [dep1, dep2, dep3, dep4, dep5]); // Too many dependencies +``` + +### ❌ Don't: Create functions inside useEffect +```typescript +useEffect(() => { + const fetchData = async () => { /* implementation */ }; // Recreated every render + fetchData(); +}, [deps]); +``` + +## Tools and Linting Rules + +1. **Enable exhaustive-deps rule**: Catches missing dependencies +2. **Use React DevTools Profiler**: Identify unnecessary re-renders +3. **Consider React Query/SWR**: For server state management +4. **Use TypeScript**: Better type safety for hook dependencies \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/package-lock.json b/src/Strata.ContinuousImprovement.Web/continuousimprovement/package-lock.json new file mode 100644 index 0000000..a125ec9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/package-lock.json @@ -0,0 +1,22224 @@ +{ + "name": "continuousimprovement", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "continuousimprovement", + "version": "0.1.0", + "dependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@strata/charts": "^0.2.0", + "@strata/core": "^5.3.0", + "@strata/eslint-config": "^3.6.0-1", + "@strata/jazz": "^0.6.0", + "@strata/logging": "^0.2.0", + "@strata/navbar": "^4.27.0", + "@strata/score": "^2.4.3", + "@strata/signalr": "^0.8.0", + "@strata/tempo": "^6.33.0", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^11.2.7", + "@types/jest": "^27.5.2", + "@types/node": "^14.18.36", + "@types/react": "^17.0.53", + "@types/react-dom": "^17.0.19", + "@types/react-router": "^5.1.20", + "@types/react-router-dom": "^5.3.3", + "@types/react-test-renderer": "^17.0.2", + "@typescript-eslint/eslint-plugin": "^4.33.0", + "chart.js": "^2.9.4", + "dayjs": "^1.11.9", + "eslint": "^7.32.0", + "eslint-plugin-flowtype": "^5.10.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "lodash": "^4.17.21", + "lodash.debounce": "^4.0.8", + "pkg.json": "^2.0.8", + "prettier": "^2.8.4", + "react": "^17.0.1", + "react-app-polyfill": "^2.0.0", + "react-chartjs-2": "^2.11.1", + "react-dom": "^17.0.1", + "react-router": "^5.3.4", + "react-router-dom": "^5.3.4", + "react-scripts": "^5.0.1", + "react-test-renderer": "^17.0.1", + "react-to-print": "^2.14.12", + "sass": "^1.64.1", + "source-map-explorer": "^2.5.3", + "typescript": "^4.9.5", + "usehooks-ts": "^3.1.1" + }, + "devDependencies": { + "@strata/test-utils": "^2.0.0", + "@types/lodash.debounce": "^4.0.7", + "ajv": "^8.16.0", + "ajv-keywords": "^5.1.0", + "schema-utils": "^4.2.0" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.23.0.tgz", + "integrity": "sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.9.tgz", + "integrity": "sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "dependencies": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", + "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", + "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", + "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", + "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-decorators": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", + "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz", + "integrity": "sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", + "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.9.tgz", + "integrity": "sha512-/VVukELzPDdci7UUsWQaSkhgnjIWXnIyRpM02ldxaVoFK96c41So8JcKT3m0gYjyv7j5FNPGS5vfELrWalkbDA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-flow": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", + "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-simple-access": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz", + "integrity": "sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", + "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz", + "integrity": "sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.38.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.9.tgz", + "integrity": "sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz", + "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz", + "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" + }, + "node_modules/@csstools/normalize.css": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", + "integrity": "sha512-YAYeJ+Xqh7fUou1d1j9XHl44BmsuThiTr4iNrgCQ3J27IbhXsxXDGZ1cXv8Qvs99d4rBbLiSKy3+WZiet32PcQ==" + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@datadog/browser-core": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-1.26.3.tgz", + "integrity": "sha512-/YrSvljK4RnjiLBX18P23PtcpYUPnSUVafy+41MatN8ZjtdzFsdK4VxQWhLFygG/Au8pvXacU1Kg7CVRk7hbMQ==", + "dependencies": { + "tslib": "^1.10.0" + } + }, + "node_modules/@datadog/browser-logs": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@datadog/browser-logs/-/browser-logs-1.26.3.tgz", + "integrity": "sha512-bVE4hWV+vkUtG3bh3o0K+rWCGlMLtDmqoBvmjylWMbmLkHM/ua1QTqgQY2m36erS6MPXgQDVy4q0BAp9vwjFPA==", + "dependencies": { + "@datadog/browser-core": "1.26.3", + "tslib": "^1.10.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/core/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/environment": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "dependencies": { + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/environment/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/fake-timers/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/globals": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/globals/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/test-result/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "dependencies": { + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.5.1", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" + }, + "node_modules/@microsoft/signalr": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.7.tgz", + "integrity": "sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw==", + "dependencies": { + "abort-controller": "^3.0.0", + "eventsource": "^2.0.2", + "fetch-cookie": "^2.0.3", + "node-fetch": "^2.6.7", + "ws": "^7.4.5" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", + "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.0", + "@parcel/watcher-darwin-arm64": "2.5.0", + "@parcel/watcher-darwin-x64": "2.5.0", + "@parcel/watcher-freebsd-x64": "2.5.0", + "@parcel/watcher-linux-arm-glibc": "2.5.0", + "@parcel/watcher-linux-arm-musl": "2.5.0", + "@parcel/watcher-linux-arm64-glibc": "2.5.0", + "@parcel/watcher-linux-arm64-musl": "2.5.0", + "@parcel/watcher-linux-x64-glibc": "2.5.0", + "@parcel/watcher-linux-x64-musl": "2.5.0", + "@parcel/watcher-win32-arm64": "2.5.0", + "@parcel/watcher-win32-ia32": "2.5.0", + "@parcel/watcher-win32-x64": "2.5.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", + "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", + "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", + "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", + "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", + "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", + "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", + "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", + "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", + "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", + "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", + "integrity": "sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==", + "dependencies": { + "ansi-html": "^0.0.9", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^4.2.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz", + "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.7.tgz", + "integrity": "sha512-Qggj4Z0AA2i5dJhzlfFSmg1Qrziu8dsdHOihROL5Kl18seO2Eh/ZaTYt2c8a/CyGaTChnFry7BEYew1+/fhSbA==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", + "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", + "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@strata/charts": { + "version": "0.2.0", + "resolved": "https://proget.ops.stratanetwork.net/npm/npm/%40strata/charts/-/charts-0.2.0.tgz", + "integrity": "sha512-1KFk+vg0Tt9YkGVeJM1Rxm/bymKJFZ10UoTdWhfbhMXQa3ctTgVHIhrXEGpksBeriBo+f3pRPDJrTixPRougng==", + "license": "ISC", + "dependencies": { + "@types/chart.js": "^2.9.29", + "@types/lodash.merge": "^4.6.6", + "chart.js": "^2.9.4", + "lodash.merge": "^4.6.2", + "react-chartjs-2": "^2.11.1" + }, + "peerDependencies": { + "react": "^16.13.1" + } + }, + "node_modules/@strata/core": { + "version": "5.3.0", + "resolved": "https://proget.ops.stratanetwork.net/npm/npm/%40strata/core/-/core-5.3.0.tgz", + "integrity": "sha512-i39o21wXlscNdOGsI5v3tjr20UHMFvGGgy9Ja4QpE3WBcy648U5GSoDaAnxmkghnsSRmMysDZPLAJth8UUUuEA==", + "license": "ISC", + "dependencies": { + "@strata/eslint-config": "^3.6.0", + "@strata/intl": "^4.1.0", + "@strata/logging": "^0.2.0", + "@strata/strataweb": "^2.304.1", + "@types/pendo-io-browser": "^2.19.1", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "downloadjs": "^1.4.7", + "eslint": "^7.32.0", + "eslint-plugin-flowtype": "^5.10.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "oidc-client-ts": "^2.4.0", + "prettier": "^2.8.8", + "react-oidc-context": "^2.3.1" + }, + "peerDependencies": { + "react": "^17.0.0" + } + }, + "node_modules/@strata/eslint-config": { + "version": "3.6.0", + "resolved": "https://proget.sdt.local/npm/npm/@strata/eslint-config/-/eslint-config-3.6.0.tgz", + "integrity": "sha512-soD6eIAlhxi20zM5I4rmzhbD/4XIYc/elSpdF+g6C7GVmfyhepJ8ty4CNMcY2HPYnfu/Uo4cjYRMrMaBFIDA1A==", + "license": "ISC", + "dependencies": { + "@typescript-eslint/parser": "^4.15.2", + "babel-eslint": "^10.1.0", + "eslint-config-prettier": "^7.1.0", + "eslint-config-react-app": "^6.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.15.2", + "eslint": "^7.20.0", + "eslint-plugin-flowtype": "^5.3.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-react": "^7.22.0", + "eslint-plugin-react-hooks": "^4.2.0", + "prettier": "^2.2.1", + "react": ">=16.13.1" + } + }, + "node_modules/@strata/intl": { + "version": "4.2.1", + "resolved": "https://proget.sdt.local/npm/npm/@strata/intl/-/intl-4.2.1.tgz", + "integrity": "sha512-zYnOGArdcux3X6aXqxl8TtST2M31i8gAAlinbMtZkHKd3FVrjwIdGFjEQDLGjmutnhr2T3CpMqQlrP/ymyfo3A==", + "license": "ISC", + "dependencies": { + "@strata/eslint-config": "^3.5.0", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "dayjs": "^1.11.8", + "eslint": "^7.29.0", + "eslint-plugin-flowtype": "^5.7.2", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react-hooks": "^4.2.0", + "numbro": "^2.3.2", + "prettier": "^2.3.1" + } + }, + "node_modules/@strata/jazz": { + "version": "0.6.1", + "resolved": "https://proget.sdt.local/npm/npm/@strata/jazz/-/jazz-0.6.1.tgz", + "integrity": "sha512-tiWtAlT/JxofKnUfUap3n67xLToTMAHPHp5pncOMb7fwGjaZmGkPrh1pSkJYQFCga7D3GD+wiXfl11x9W39zyg==", + "license": "ISC", + "dependencies": { + "@strata/core": "^5.0.0", + "@strata/eslint-config": "^3.6.0-1", + "@strata/settings": "^0.3.0", + "@strata/tempo": "^6.12.0", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "eslint": "^8.56.0", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsx-a11y": "^6.8.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "prettier": "^3.2.5" + }, + "peerDependencies": { + "react": "^16.13.1" + } + }, + "node_modules/@strata/jazz/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/jazz/node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@strata/jazz/node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead" + }, + "node_modules/@strata/jazz/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@strata/jazz/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@strata/jazz/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/@strata/jazz/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@strata/jazz/node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/jazz/node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/@strata/jazz/node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/@strata/jazz/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/jazz/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/jazz/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/jazz/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/jazz/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@strata/jazz/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/jazz/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@strata/jazz/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/@strata/jazz/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/jazz/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/jazz/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/jazz/node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/@strata/jazz/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/logging": { + "version": "0.2.0", + "resolved": "https://proget.ops.stratanetwork.net/npm/npm/%40strata/logging/-/logging-0.2.0.tgz", + "integrity": "sha512-ryVU6EDwPpFE/bwbzJCvckfo5VHHOrBwdZi8TQo9q+Z5nbTg5hdJj+l9E5I+278g9iQ72dZq932WGyf/nR2N0w==", + "license": "ISC", + "dependencies": { + "@datadog/browser-logs": "^1.25.2", + "@strata/eslint-config": "^3.5.0", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "eslint": "^7.29.0", + "eslint-plugin-flowtype": "^5.7.2", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react-hooks": "^4.2.0", + "prettier": "^2.3.1" + }, + "peerDependencies": { + "react": "^16.13.1", + "react-router-dom": "^5.2.0" + } + }, + "node_modules/@strata/navbar": { + "version": "4.27.0", + "resolved": "https://proget.ops.stratanetwork.net/npm/npm/%40strata/navbar/-/navbar-4.27.0.tgz", + "integrity": "sha512-u2O29s/7PJbvgO1wuHT3ZLuAC7dyrVMBBVxpuFp5obGTJplEBf440r5G+17X46CRyTTbXfZSuU5rxt9tJrXbJw==", + "dependencies": { + "@strata/core": "^5.0.0" + }, + "peerDependencies": { + "react": "^16.13.1" + } + }, + "node_modules/@strata/score": { + "version": "2.4.3", + "resolved": "https://proget.ops.stratanetwork.net/npm/npm/%40strata/score/-/score-2.4.3.tgz", + "integrity": "sha512-dVWj1MhmE9d9arB448yHkQVaTrt0Ee7WkO/4R7DqPHTDV3Iwval10xCrgLUaRFDTecN6vfKaowbzLazm5zOv4Q==", + "license": "ISC", + "dependencies": { + "@strata/core": "^5.0.0", + "@strata/eslint-config": "^3.5.0", + "@strata/tempo": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "eslint": "^7.29.0", + "eslint-plugin-flowtype": "^5.7.2", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react-hooks": "^4.2.0", + "prettier": "^2.3.1" + }, + "peerDependencies": { + "react": "^16.13.1" + } + }, + "node_modules/@strata/settings": { + "version": "0.3.0", + "resolved": "https://proget.sdt.local/npm/npm/@strata/settings/-/settings-0.3.0.tgz", + "integrity": "sha512-yLuSyILUBWFadtP5zRsj9S9F+UIo2P5I9D2RlkmwSYffY6pt3TBoMkK4RA7pXSTeF/Pq6sEqc36MkdiypICISA==", + "license": "ISC", + "dependencies": { + "@strata/core": "^5.0.0", + "@strata/eslint-config": "^3.6.0-1", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "eslint": "^8.56.0", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsx-a11y": "^6.8.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "prettier": "^3.2.5" + }, + "peerDependencies": { + "react": "^16.13.1" + } + }, + "node_modules/@strata/settings/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/settings/node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@strata/settings/node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead" + }, + "node_modules/@strata/settings/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@strata/settings/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@strata/settings/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/@strata/settings/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@strata/settings/node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/settings/node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/@strata/settings/node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/@strata/settings/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/settings/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/settings/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@strata/settings/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/settings/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@strata/settings/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/settings/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@strata/settings/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/@strata/settings/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/settings/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/settings/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/settings/node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/@strata/settings/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@strata/signalr": { + "version": "0.8.1", + "resolved": "https://proget.sdt.local/npm/npm/@strata/signalr/-/signalr-0.8.1.tgz", + "integrity": "sha512-gtBvLDOO3JmVSHS8ZF3g82+7O4QRXaby7s0fcRZn1RSnmhzZiXXQdDyROuV7SipZOJ4NJpT6XOJ3crhVTkiWOw==", + "license": "ISC", + "dependencies": { + "@microsoft/signalr": "^8.0.0", + "@strata/core": "^5.0.0", + "@strata/eslint-config": "^3.5.0", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "eslint": "^7.29.0", + "eslint-plugin-flowtype": "^5.7.2", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react-hooks": "^4.2.0", + "prettier": "^2.3.1" + }, + "peerDependencies": { + "react": "^16.13.1" + } + }, + "node_modules/@strata/strataweb": { + "version": "2.330.0", + "resolved": "https://proget.sdt.local/npm/npm/@strata/strataweb/-/strataweb-2.330.0.tgz", + "integrity": "sha512-qidl/Y46KpyDgsW5zNWPewr4IE0xTohfOyt18n917h84nDIDxzeP1/85V7Vc04+JQsjPsaFlkhyTaiZT6f+Tyg==", + "license": "ISC" + }, + "node_modules/@strata/styles": { + "version": "3.2.0", + "resolved": "https://proget.ops.stratanetwork.net/npm/npm/%40strata/styles/-/styles-3.2.0.tgz", + "integrity": "sha512-2nO1wWgs4LTMbFiqkF37ATGQnCDev2QJ9mQgubJP5VixTJPiw9I9SF7aPSgvRGRddAiYeTGc+lZD5ao8/bkIXw==" + }, + "node_modules/@strata/tempo": { + "version": "6.33.0", + "resolved": "https://proget.ops.stratanetwork.net/npm/npm/%40strata/tempo/-/tempo-6.33.0.tgz", + "integrity": "sha512-7JYUTLem/xuHKSlQna0BMeufsuLLnpCj5hBhI5LTaihkgTqXf9LW81MyiNtVoBNEefta8ppTdfVvn6sqw+Yj+w==", + "dependencies": { + "@ant-design/icons": "^5.1.4", + "@strata/intl": "^4.1.0", + "@strata/logging": "0.0.3", + "@strata/styles": "^3.2.0", + "@types/url-parse": "^1.4.3", + "antd": "^5.25.3", + "async-validator": "^3.4.0", + "bootstrap": "^4.5.0", + "less": "^3.11.3", + "lodash": "^4.17.15", + "primereact": "6.3.2", + "react-svg": "^11.0.28", + "react-transition-group": "^4.4.1", + "url-parse": "^1.4.7" + }, + "peerDependencies": { + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-router-dom": "^5.2.0" + } + }, + "node_modules/@strata/tempo/node_modules/@strata/logging": { + "version": "0.0.3", + "resolved": "https://proget.sdt.local/npm/npm/@strata/logging/-/logging-0.0.3.tgz", + "integrity": "sha1-ZJTBiN/nYBgPxlbFjrzrozpoHPI=", + "license": "ISC", + "dependencies": { + "@datadog/browser-logs": "^1.25.2" + }, + "peerDependencies": { + "react": "^16.13.1", + "react-router-dom": "^5.2.0" + } + }, + "node_modules/@strata/test-utils": { + "version": "2.0.0", + "resolved": "https://proget.sdt.local/npm/npm/@strata/test-utils/-/test-utils-2.0.0.tgz", + "integrity": "sha512-i95v2u4TWB02DZ809txjB5PZ6AMuX8VpvA4PtFDOVBzyWTYZb48lL8b+mRLmFHtgiSKS9jIUVWPODUs8ZZi/SA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@strata/eslint-config": "^3.6.0", + "@typescript-eslint/eslint-plugin": "^4.15.2", + "eslint": "^7.20.0", + "eslint-plugin-flowtype": "^5.3.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-react": "^7.22.0", + "eslint-plugin-react-hooks": "^4.2.0", + "prettier": "^2.3.1" + }, + "peerDependencies": { + "@testing-library/jest-dom": "5.16.1", + "@testing-library/react": "12.1.2", + "@testing-library/user-event": "13.5.0", + "react": "^16.13.1", + "react-dom": "^16.13.1" + } + }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", + "dependencies": { + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", + "dependencies": { + "@babel/types": "^7.12.6" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", + "dependencies": { + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", + "svg-parser": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", + "dependencies": { + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", + "svgo": "^1.2.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/webpack": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", + "loader-utils": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@tanem/svg-injector": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@tanem/svg-injector/-/svg-injector-8.2.5.tgz", + "integrity": "sha512-jQ6UPPY2oSu2d0YLngtvnOIoKheYsB/s2bNP/QdsLrsvlz+PzUUO4RUWBN3+8clZPi2SI5OMF/YoTiTKt5v2xw==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "content-type": "^1.0.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@tanem/svg-injector/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/@testing-library/dom": { + "version": "7.31.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz", + "integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^4.2.0", + "aria-query": "^4.2.2", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.6", + "lz-string": "^1.4.4", + "pretty-format": "^26.6.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dependencies": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", + "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==", + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/react": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz", + "integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^7.28.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chart.js": { + "version": "2.9.41", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.41.tgz", + "integrity": "sha512-3dvkDvueckY83UyUXtJMalYoH6faOLkWQoaTlJgB4Djde3oORmNP0Jw85HtzTuXyliUHcdp704s0mZFQKio/KQ==", + "dependencies": { + "moment": "^2.10.2" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.12", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", + "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "27.5.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", + "integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==", + "dependencies": { + "jest-matcher-utils": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "node_modules/@types/lodash": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==" + }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.merge": { + "version": "4.6.9", + "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.9.tgz", + "integrity": "sha512-23sHDPmzd59kUgWyKGiOMO2Qb9YtqRO/x4IhkgNUiPQ1+5MUVqi6bCZeq9nBJ17msjIMbEIO5u+XW4Kz6aGUhQ==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/pendo-io-browser": { + "version": "2.19.1", + "resolved": "https://registry.npmjs.org/@types/pendo-io-browser/-/pendo-io-browser-2.19.1.tgz", + "integrity": "sha512-bk/0qU1mSSVhDq0db6Zvtvwh8XXf5t5SwlDeDZUVkwKb8am07YAZkURSl/gDxgcqYS499Vw4vpwVYIYzSFuUZw==" + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" + }, + "node_modules/@types/q": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", + "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==" + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/react": { + "version": "17.0.83", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz", + "integrity": "sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", + "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", + "dependencies": { + "@types/react": "^17" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/react-test-renderer": { + "version": "17.0.9", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.9.tgz", + "integrity": "sha512-bOfxcu5oZ+KxvACScbkTwZ4eGCtZFTz4VZCOVAIfGbThxqiXSIGipKVG8ubaYBXquUSQROzNIUzviWdSnnAlzg==", + "dependencies": { + "@types/react": "^17" + } + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" + }, + "node_modules/@types/testing-library__jest-dom": { + "version": "5.14.9", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", + "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", + "dependencies": { + "@types/jest": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/@types/url-parse": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.11.tgz", + "integrity": "sha512-FKvKIqRaykZtd4n47LbK/W/5fhQQ1X7cxxzG9A48h0BGN+S04NH7ervcCjM8tyR0lyGru83FAHSmw2ObgKoESg==" + }, + "node_modules/@types/ws": { + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", + "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "dependencies": { + "@typescript-eslint/experimental-utils": "4.33.0", + "@typescript-eslint/scope-manager": "4.33.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", + "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", + "dependencies": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", + "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "dependencies": { + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "debug": "^4.3.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", + "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "dependencies": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", + "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", + "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", + "dependencies": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", + "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "dependencies": { + "@typescript-eslint/types": "4.33.0", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abs": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/abs/-/abs-1.3.14.tgz", + "integrity": "sha512-PrS26IzwKLWwuURpiKl8wRmJ2KdR/azaVrLEBWG/TALwT20Y7qjtYp1qcMLHA4206hBHY5phv3w4pjf9NPv4Vw==", + "dependencies": { + "ul": "^5.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "5.26.4", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.26.4.tgz", + "integrity": "sha512-e1EnOvEkvvqcQ18dxfzChBJyJACyih13WpNf2OtnP9z2POh/SF0fXL+ynUemT1zfr+p+P1po/tmHXaMc5PMghg==", + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.0.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.2.7", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.0", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.8", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.51.1", + "rc-tabs": "~15.6.1", + "rc-textarea": "~1.10.0", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.9.2", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.7.tgz", + "integrity": "sha512-mzmiUCVwtiD4lgxYP8g7IYy8El8p2CSMePvIbTS7gchKir/L1fgJrk0yDKmAX6mnRQFKNADYIk8nNlTris5H1Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-array-method-boxes-properly": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/async-validator": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz", + "integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "eslint": ">= 4.12.1" + } + }, + "node_modules/babel-eslint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-jest/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-loader": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", + "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.4", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/babel-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/babel-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/babel-loader/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-named-asset-import": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", + "peerDependencies": { + "@babel/core": "^7.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + }, + "node_modules/bfj": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz", + "integrity": "sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==", + "dependencies": { + "bluebird": "^3.7.2", + "check-types": "^11.2.3", + "hoopy": "^0.1.4", + "jsonpath": "^1.1.1", + "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "engines": { + "node": "*" + } + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/bootstrap": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "jquery": "1.9.1 - 3", + "popper.js": "^1.16.1" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camel-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001678", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001678.tgz", + "integrity": "sha512-RR+4U/05gNtps58PEBDZcPWTgEO2MBeoPZ96aQcjmfkBWRIDfN451fW2qyDA9/+HohLLIL5GqiMwA+IB1pWarw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/capture-stack-trace": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz", + "integrity": "sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/chart.js": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", + "dependencies": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "node_modules/chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "dependencies": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + } + }, + "node_modules/chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "dependencies": { + "color-name": "^1.0.0" + } + }, + "node_modules/chartjs-color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/chartjs-color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/check-types": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", + "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==" + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dependencies": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/coa/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/coa/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/coa/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/coa/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-js": { + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", + "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", + "dependencies": { + "browserslist": "^4.24.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.39.0.tgz", + "integrity": "sha512-7fEcWwKI4rJinnK+wLTezeg2smbFFdSBP6E2kQZNbnzM2s1rpKQ6aaRteZSSg7FLU3P0HGGVo/gbpfanU36urg==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==", + "dependencies": { + "capture-stack-trace": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "dependencies": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" + }, + "node_modules/cssdb": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.11.2.tgz", + "integrity": "sha512-lhQ32TFkc1X4eTefGfYPvgovRSzIMofHkigfH8nWtyRL4XJLsRhJFreRvEgKzept7x1rjBuy3J/MurXLaFxW/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ] + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/deffy": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/deffy/-/deffy-2.2.4.tgz", + "integrity": "sha512-pLc9lsbsWjr6RxmJ2OLyvm+9l4j1yK69h+TML/gUit/t3vTijpkNGh8LioaJYTGO7F25m6HZndADcUOo2PsiUg==", + "dependencies": { + "typpy": "^2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "node_modules/detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "dependencies": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "bin": { + "detect": "bin/detect-port", + "detect-port": "bin/detect-port" + }, + "engines": { + "node": ">= 4.2.1" + } + }, + "node_modules/detect-port-alt/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/detect-port-alt/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==" + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "deprecated": "Use your platform's native DOMException instead", + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" + }, + "node_modules/downloadjs": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", + "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.52", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.52.tgz", + "integrity": "sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ==" + }, + "node_modules/emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/err": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/err/-/err-1.1.1.tgz", + "integrity": "sha512-N97Ybd2jJHVQ+Ft3Q5+C2gM3kgygkdeQmEqbN2z15UTVyyEsIwLA1VK39O1DHEJhXbwIFcJLqm6iARNhFANcQA==", + "dependencies": { + "typpy": "^2.2.0" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", + "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.3", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==" + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz", + "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-react-app": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", + "integrity": "sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==", + "dependencies": { + "confusing-browser-globals": "^1.0.10" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0", + "@typescript-eslint/parser": "^4.0.0", + "babel-eslint": "^10.0.0", + "eslint": "^7.5.0", + "eslint-plugin-flowtype": "^5.2.0", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-jest": "^24.0.0", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-react": "^7.20.3", + "eslint-plugin-react-hooks": "^4.0.8", + "eslint-plugin-testing-library": "^3.9.0" + }, + "peerDependenciesMeta": { + "eslint-plugin-jest": { + "optional": true + }, + "eslint-plugin-testing-library": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.10.0.tgz", + "integrity": "sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw==", + "dependencies": { + "lodash": "^4.17.15", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.1.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", + "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.1.0", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "dependencies": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exec-limiter": { + "version": "3.2.13", + "resolved": "https://registry.npmjs.org/exec-limiter/-/exec-limiter-3.2.13.tgz", + "integrity": "sha512-86Ri699bwiHZVBzTzNj8gspqAhCPchg70zPVWIh3qzUOA1pUMcb272Em3LPk8AE0mS95B9yMJhtqF8vFJAn0dA==", + "dependencies": { + "limit-it": "^3.0.0", + "typpy": "^2.1.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/expect/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/expect/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/expect/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fetch-cookie": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz", + "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==", + "dependencies": { + "set-cookie-parser": "^2.4.8", + "tough-cookie": "^4.0.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/file-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/file-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/form-data": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.2.tgz", + "integrity": "sha512-sJe+TQb2vIaIyO783qN6BlMYWMw3WBOHA1Ay2qxsnjuafEOQFJ2JakedOQirT6D5XPRxDvS7AHYyem9fTpb4LQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.name": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/function.name/-/function.name-1.0.13.tgz", + "integrity": "sha512-mVrqdoy5npWZyoXl4DxCeuVF6delDcQjVS9aPdvLYlBxtMTZDR2B5GVEQEoM1jJyspCqg3C0v4ABkLE7tp9xFA==", + "dependencies": { + "noop6": "^1.0.1" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/git-package-json": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/git-package-json/-/git-package-json-1.4.10.tgz", + "integrity": "sha512-DRAcvbzd2SxGK7w8OgYfvKqhFliT5keX0lmSmVdgScgf1kkl5tbbo7Pam6uYoCa1liOiipKxQZG8quCtGWl/fA==", + "dependencies": { + "deffy": "^2.2.1", + "err": "^1.1.1", + "gry": "^5.0.0", + "normalize-package-data": "^2.3.5", + "oargv": "^3.4.1", + "one-by-one": "^3.1.0", + "r-json": "^1.2.1", + "r-package-json": "^1.0.0", + "tmp": "0.0.28" + } + }, + "node_modules/git-source": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/git-source/-/git-source-1.1.10.tgz", + "integrity": "sha512-XZZ7ZgnLL35oLgM/xjnLYgtlKlxJG0FohC1kWDvGkU7s1VKGXK0pFF/g1itQEwQ3D+uTQzBnzPi8XbqOv7Wc1Q==", + "dependencies": { + "git-url-parse": "^5.0.1" + } + }, + "node_modules/git-up": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-1.2.1.tgz", + "integrity": "sha512-SRVN3rOLACva8imc7BFrB6ts5iISWKH1/h/1Z+JZYoUI7UVQM7gQqk4M2yxUENbq2jUUT09NEND5xwP1i7Ktlw==", + "dependencies": { + "is-ssh": "^1.0.0", + "parse-url": "^1.0.0" + } + }, + "node_modules/git-url-parse": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-5.0.1.tgz", + "integrity": "sha512-4uSiOgrryNEMBX+gTWogenYRUh2j1D+95STTSEF2RCTgLkfJikl8c7BGr0Bn274hwuxTsbS2/FQ5pVS9FoXegQ==", + "dependencies": { + "git-up": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-5.6.0.tgz", + "integrity": "sha512-MnypzkaW8dldA8AbJFjMs7y14+ykd2V8JCLKSvX1Gmzx1alH3Y+3LArywHDoAF2wS3pnZp4gacoYtvqBeF6drQ==", + "dependencies": { + "create-error-class": "^3.0.1", + "duplexer2": "^0.1.4", + "is-plain-obj": "^1.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "node-status-codes": "^1.0.0", + "object-assign": "^4.0.1", + "parse-json": "^2.1.0", + "pinkie-promise": "^2.0.0", + "read-all-stream": "^3.0.0", + "readable-stream": "^2.0.5", + "timed-out": "^2.0.0", + "unzip-response": "^1.0.0", + "url-parse-lax": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, + "node_modules/gry": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/gry/-/gry-5.0.8.tgz", + "integrity": "sha512-meq9ZjYVpLzZh3ojhTg7IMad9grGsx6rUUKHLqPnhLXzJkRQvEL2U3tQpS5/WentYTtHtxkT3Ew/mb10D6F6/g==", + "dependencies": { + "abs": "^1.2.1", + "exec-limiter": "^3.0.0", + "one-by-one": "^3.0.0", + "ul": "^5.0.0" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", + "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==" + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ssh": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "dependencies": { + "protocols": "^2.0.1" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterate-object": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.4.tgz", + "integrity": "sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==" + }, + "node_modules/iterator.prototype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", + "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "dependencies": { + "@jest/core": "^27.5.1", + "import-local": "^3.0.2", + "jest-cli": "^27.5.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "dependencies": { + "@jest/types": "^27.5.1", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-changed-files/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "dependencies": { + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-cli/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "dependencies": { + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-config/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-docblock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-environment-jsdom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-environment-node": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-environment-node/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-haste-map/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-haste-map/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-jasmine2/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-jasmine2/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", + "dependencies": { + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-mock/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-serializer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^27.5.1", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "leven": "^3.1.0", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", + "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", + "dependencies": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^28.0.0", + "jest-watcher": "^28.0.0", + "slash": "^4.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-watch-typeahead/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/jest-watch-typeahead/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", + "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watcher": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "dependencies": { + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.5.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + } + }, + "node_modules/jsonpath/node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/launch-editor": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/less": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/less/-/less-3.13.1.tgz", + "integrity": "sha512-SwA1aQXGUvp+P5XdZslUOhhLnClSLIjWvJhmd+Vgib5BFIr9lMNlQwmwUNOjXThF/A0x+MCYYPeWEfeWiLRnTw==", + "dependencies": { + "copy-anything": "^2.0.1", + "tslib": "^1.10.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "native-request": "^1.0.5", + "source-map": "~0.6.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/limit-it": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/limit-it/-/limit-it-3.2.10.tgz", + "integrity": "sha512-T0NK99pHnkimldr1WUqvbGV1oWDku/xC9J/OqzJFsV1jeOS6Bwl8W7vkeQIBqwiON9dTALws+rX/XPMQqWerDQ==", + "dependencies": { + "typpy": "^2.0.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lower-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/native-request": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/native-request/-/native-request-1.1.2.tgz", + "integrity": "sha512-/etjwrK0J4Ebbcnt35VMWnfiUX/B04uwGJxyJInagxDqf2z5drSt/lsOvEMWGYunz1kaLZAFrV4NDAbOoDKvAQ==", + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/no-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "optional": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" + }, + "node_modules/node-status-codes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", + "integrity": "sha512-1cBMgRxdMWE8KeWCqk2RIOrvUb0XCwYfEsY5/y2NlXyq4Y/RumnOZvTj4Nbr77+Vb2C+kyBoRTdkNOS8L3d/aQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/noop6": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/noop6/-/noop6-1.0.9.tgz", + "integrity": "sha512-DB3Hwyd89dPr5HqEPg3YHjzvwh/mCqizC1zZ8vyofqc+TQRyPDnT4wgXXbLGF4z9YAzwwTLi8pNLhGqcbSjgkA==" + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/numbro": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/numbro/-/numbro-2.5.0.tgz", + "integrity": "sha512-xDcctDimhzko/e+y+Q2/8i3qNC9Svw1QgOkSkQoO0kIPI473tR9QRbo2KP88Ty9p8WbPy+3OpTaAIzehtuHq+A==", + "dependencies": { + "bignumber.js": "^8 || ^9" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nwsapi": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==" + }, + "node_modules/oargv": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/oargv/-/oargv-3.4.10.tgz", + "integrity": "sha512-SXaMANv9sr7S/dP0vj0+Ybipa47UE1ntTWQ2rpPRhC6Bsvfl+Jg03Xif7jfL0sWKOYWK8oPjcZ5eJ82t8AP/8g==", + "dependencies": { + "iterate-object": "^1.1.0", + "ul": "^5.0.0" + } + }, + "node_modules/obj-def": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/obj-def/-/obj-def-1.0.9.tgz", + "integrity": "sha512-bQ4ya3VYD6FAA1+s6mEhaURRHSmw4+sKaXE6UyXZ1XDYc5D+c7look25dFdydmLd18epUegh398gdDkMUZI9xg==", + "dependencies": { + "deffy": "^2.2.2" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz", + "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==", + "dependencies": { + "array.prototype.reduce": "^1.0.6", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "gopd": "^1.0.1", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "node_modules/oidc-client-ts": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-2.4.1.tgz", + "integrity": "sha512-IxlGMsbkZPsHJGCliWT3LxjUcYzmiN21656n/Zt2jDncZlBFc//cd8WqFF0Lt681UT3AImM57E6d4N53ziTCYA==", + "dependencies": { + "crypto-js": "^4.2.0", + "jwt-decode": "^3.1.2" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-by-one": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/one-by-one/-/one-by-one-3.2.8.tgz", + "integrity": "sha512-HR/pSzZdm46Xqj58K+Bu64kMbSTw8/u77AwWvV+rprO/OsuR++pPlkUJn+SmwqBGRgHKwSKQ974V3uls7crIeQ==", + "dependencies": { + "obj-def": "^1.0.0", + "sliced": "^1.0.1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-2.4.0.tgz", + "integrity": "sha512-PRg65iXMTt/uK8Rfh5zvzkUbfAPitF17YaCY+IbHsYgksiLvtzWWTUildHth3mVaZ7871OJ7gtP4LBRBlmAdXg==", + "dependencies": { + "got": "^5.0.0", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "node_modules/package-json-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/package-json-path/-/package-json-path-1.0.9.tgz", + "integrity": "sha512-uNu7f6Ef7tQHZRnkyVnCtzdSYVN9uBtge/sG7wzcUaawFWkPYUq67iXxRGrQSg/q0tzxIB8jSyIYUKjG2Jn//A==", + "dependencies": { + "abs": "^1.2.1" + } + }, + "node_modules/package-json/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/param-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-url": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-1.3.11.tgz", + "integrity": "sha512-1wj9nkgH/5EboDxLwaTMGJh3oH3f+Gue+aGdh631oCqoSBpokzmMmOldvOeBPtB8GJBYJbaF93KPzlkU+Y1ksg==", + "dependencies": { + "is-ssh": "^1.3.0", + "protocols": "^1.4.0" + } + }, + "node_modules/parse-url/node_modules/protocols": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", + "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==" + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/path-to-regexp": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg.json": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/pkg.json/-/pkg.json-2.0.8.tgz", + "integrity": "sha512-iIBceNPwO452wLbF73GCV5z/TY9UydLWEV+wBjegAObPF/PihDL9ZUr2g2EFI7XqPFMTLEX/QtL63Z+zzFZ7yA==", + "dependencies": { + "git-package-json": "^1.4.0", + "git-source": "^1.1.0", + "package-json": "^2.3.1" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-browser-comments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "browserslist": ">=4", + "postcss": ">=8" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-flexbugs-fixes": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "peerDependencies": { + "postcss": "^8.1.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-normalize": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", + "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", + "dependencies": { + "@csstools/normalize.css": "*", + "postcss-browser-comments": "^4", + "sanitize.css": "*" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "browserslist": ">= 4", + "postcss": ">= 8" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.3.tgz", + "integrity": "sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag==", + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.1.1", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.13", + "browserslist": "^4.21.4", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.1.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.10", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.2.0", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/postcss-svgo/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/postcss-svgo/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/postcss-svgo/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/primereact": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/primereact/-/primereact-6.3.2.tgz", + "integrity": "sha512-lfvipaSD8uSJhuFfw5Akn2AE/mr/494HtiPfMFdD1aRbTkpxk0EKNlkQNqHEdNFq8c6nrIwFTX2GyiSNWSc/Uw==" + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/protocols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "optional": true + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/r-json": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/r-json/-/r-json-1.3.0.tgz", + "integrity": "sha512-xesd+RHCpymPCYd9DvDvUr1w1IieSChkqYF1EpuAYrvCfLXji9NP36DvyYZJZZB5soVDvZ0WUtBoZaU1g5Yt9A==", + "dependencies": { + "w-json": "1.3.10" + } + }, + "node_modules/r-package-json": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/r-package-json/-/r-package-json-1.0.9.tgz", + "integrity": "sha512-G4Vpf1KImWmmPFGdtWQTU0L9zk0SjqEC4qs/jE7AQ+Ylmr5kizMzGeC4wnHp5+ijPqNN+2ZPpvyjVNdN1CDVcg==", + "dependencies": { + "package-json-path": "^1.0.0", + "r-json": "^1.2.1" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.0.tgz", + "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.4.1.tgz", + "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz", + "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.8", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.8.tgz", + "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.51.1", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.51.1.tgz", + "integrity": "sha512-5iq15mTHhvC42TlBLRCoCBLoCmGlbRZAlyF21FonFnS/DIC8DeRqnmdyVREwt2CFbPceM0zSNdEeVfiGaqYsKw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.6.1.tgz", + "integrity": "sha512-/HzDV1VqOsUWyuC0c6AkxVYFjvx9+rFPKZ32ejxX0Uc7QCzcEjTA9/xMgv4HemPKwzBNX8KhGVbbumDjnj92aA==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.0.tgz", + "integrity": "sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.9.2.tgz", + "integrity": "sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/rc-virtual-list": { + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.1.tgz", + "integrity": "sha512-DCapO2oyPqmooGhxBuXHM4lFuX+sshQwWqqkuyFA+4rShLe//+GEPVwiDgO+jKtKHtbeYwZoNvetwfHdOf+iUQ==", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-app-polyfill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz", + "integrity": "sha512-0sF4ny9v/B7s6aoehwze9vJNWcmCemAUYBVasscVr92+UYiEqDXOxfKjXN685mDaMRNF3WdhHQs76oTODMocFA==", + "dependencies": { + "core-js": "^3.6.5", + "object-assign": "^4.1.1", + "promise": "^8.1.0", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "whatwg-fetch": "^3.4.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-app-polyfill/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/react-chartjs-2": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.11.2.tgz", + "integrity": "sha512-hcPS9vmRJeAALPPf0uo02BiD8BDm0HNmneJYTZVR74UKprXOpql+Jy1rVuj93rKw0Jfx77mkcRfXPxTe5K83uw==", + "dependencies": { + "lodash": "^4.17.19", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "chart.js": "^2.3", + "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0", + "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/react-dev-utils": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/react-dev-utils/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/react-dev-utils/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "peerDependencies": { + "react": "17.0.2" + } + }, + "node_modules/react-error-overlay": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", + "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-oidc-context": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/react-oidc-context/-/react-oidc-context-2.4.0.tgz", + "integrity": "sha512-7xbEXlBBlWBQfdSibjeA7jC5w1oKTvZVAodd9A+fmqDRt4bD+gGODWgGT8dX+a0S96Vfo7lzIrEBP7eZCFkwWw==", + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "oidc-client-ts": "^2.4.1", + "react": ">=16.8.0" + } + }, + "node_modules/react-refresh": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", + "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-scripts": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", + "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", + "dependencies": { + "@babel/core": "^7.16.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@svgr/webpack": "^5.5.0", + "babel-jest": "^27.4.2", + "babel-loader": "^8.2.3", + "babel-plugin-named-asset-import": "^0.3.8", + "babel-preset-react-app": "^10.0.1", + "bfj": "^7.0.2", + "browserslist": "^4.18.1", + "camelcase": "^6.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "css-loader": "^6.5.1", + "css-minimizer-webpack-plugin": "^3.2.0", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "eslint": "^8.3.0", + "eslint-config-react-app": "^7.0.1", + "eslint-webpack-plugin": "^3.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.0", + "html-webpack-plugin": "^5.5.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^27.4.3", + "jest-resolve": "^27.4.2", + "jest-watch-typeahead": "^1.0.0", + "mini-css-extract-plugin": "^2.4.5", + "postcss": "^8.4.4", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-loader": "^6.2.1", + "postcss-normalize": "^10.0.1", + "postcss-preset-env": "^7.0.1", + "prompts": "^2.4.2", + "react-app-polyfill": "^3.0.0", + "react-dev-utils": "^12.0.1", + "react-refresh": "^0.11.0", + "resolve": "^1.20.0", + "resolve-url-loader": "^4.0.0", + "sass-loader": "^12.3.0", + "semver": "^7.3.5", + "source-map-loader": "^3.0.0", + "style-loader": "^3.3.1", + "tailwindcss": "^3.0.2", + "terser-webpack-plugin": "^5.2.5", + "webpack": "^5.64.4", + "webpack-dev-server": "^4.6.0", + "webpack-manifest-plugin": "^4.0.2", + "workbox-webpack-plugin": "^6.4.1" + }, + "bin": { + "react-scripts": "bin/react-scripts.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + }, + "peerDependencies": { + "react": ">= 16", + "typescript": "^3.2.1 || ^4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/react-scripts/node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/react-scripts/node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead" + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/experimental-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", + "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", + "dependencies": { + "@typescript-eslint/utils": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/react-scripts/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/react-scripts/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/react-scripts/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/react-scripts/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/react-scripts/node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/react-scripts/node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/react-scripts/node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/react-scripts/node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/eslint-plugin-testing-library": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", + "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==", + "dependencies": { + "@typescript-eslint/utils": "^5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/react-scripts/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/react-scripts/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/react-scripts/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/react-scripts/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-scripts/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/react-scripts/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-scripts/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/react-scripts/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/react-scripts/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-scripts/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-scripts/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-scripts/node_modules/react-app-polyfill": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", + "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", + "dependencies": { + "core-js": "^3.19.2", + "object-assign": "^4.1.1", + "promise": "^8.1.0", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.9", + "whatwg-fetch": "^3.6.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-scripts/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/react-scripts/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-scripts/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-svg": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/react-svg/-/react-svg-11.2.5.tgz", + "integrity": "sha512-uUMhRVdU//5qktiTSO2W6nQeHRLwYA1FsWANQFB5O33UgT2uT0V5wVapu46+wz1mx6wt1j53+1aA8QuNn4lJ6A==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "@tanem/svg-injector": "^8.2.4", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^15.5.4 || ^16.0.0 || ^17.0.0", + "react-dom": "^15.5.4 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/react-test-renderer": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", + "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^17.0.2", + "react-shallow-renderer": "^16.13.1", + "scheduler": "^0.20.2" + }, + "peerDependencies": { + "react": "17.0.2" + } + }, + "node_modules/react-to-print": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.15.1.tgz", + "integrity": "sha512-1foogIFbCpzAVxydkhBiDfMiFYhIMphiagDOfcG4X/EcQ+fBPqJ0rby9Wv/emzY1YLkIQy/rEgOrWQT+rBKhjw==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-all-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", + "integrity": "sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w==", + "dependencies": { + "pinkie-promise": "^2.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/regexpu-core": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", + "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.11.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==" + }, + "node_modules/regjsparser": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.2.tgz", + "integrity": "sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "node_modules/resolve-url-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", + "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^7.0.35", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=8.9" + }, + "peerDependencies": { + "rework": "1.0.1", + "rework-visit": "1.0.0" + }, + "peerDependenciesMeta": { + "rework": { + "optional": true + }, + "rework-visit": { + "optional": true + } + } + }, + "node_modules/resolve-url-loader/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/resolve-url-loader/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, + "node_modules/resolve-url-loader/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sanitize.css": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", + "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" + }, + "node_modules/sass": { + "version": "1.80.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.6.tgz", + "integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-explorer": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.3.tgz", + "integrity": "sha512-qfUGs7UHsOBE5p/lGfQdaAj/5U/GWYBw2imEpD6UQNkqElYonkow8t+HBL1qqIl3CuGZx7n8/CQo4x1HwSHhsg==", + "dependencies": { + "btoa": "^1.2.1", + "chalk": "^4.1.0", + "convert-source-map": "^1.7.0", + "ejs": "^3.1.5", + "escape-html": "^1.0.3", + "glob": "^7.1.6", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "open": "^7.3.1", + "source-map": "^0.7.4", + "temp": "^0.9.4", + "yargs": "^16.2.0" + }, + "bin": { + "sme": "bin/cli.js", + "source-map-explorer": "bin/cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/source-map-explorer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/source-map-explorer/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/source-map-explorer/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map-explorer/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", + "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", + "dependencies": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead" + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" + }, + "node_modules/static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "dependencies": { + "escodegen": "^1.8.1" + } + }, + "node_modules/static-eval/node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/static-eval/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/static-eval/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==" + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + }, + "node_modules/svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "dependencies": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/svgo/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/svgo/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/svgo/node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/svgo/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/svgo/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/svgo/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/synckit/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/table": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", + "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", + "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/throat": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", + "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "node_modules/timed-out": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz", + "integrity": "sha512-pqqJOi1rF5zNs/ps4vmbE4SFCrM4iR7LW+GHAsHqO/EumqbIWceioevYLM5xZRgQSH6gFgL9J/uB7EcJhQ9niQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/tmp": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", + "integrity": "sha512-c2mmfiBmND6SOVxzogm1oda0OJ1HZVIk/5n26N59dDTh80MUeavpiCls4PGAdkX1PFkKokLpcf7prSjCeXLsJg==", + "dependencies": { + "os-tmpdir": "~1.0.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/typpy": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/typpy/-/typpy-2.3.13.tgz", + "integrity": "sha512-vOxIcQz9sxHi+rT09SJ5aDgVgrPppQjwnnayTrMye1ODaU8gIZTDM19t9TxmEElbMihx2Nq/0/b/MtyKfayRqA==", + "dependencies": { + "function.name": "^1.0.3" + } + }, + "node_modules/ul": { + "version": "5.2.15", + "resolved": "https://registry.npmjs.org/ul/-/ul-5.2.15.tgz", + "integrity": "sha512-svLEUy8xSCip5IWnsRa0UOg+2zP0Wsj4qlbjTmX6GJSmvKMHADBuHOm1dpNkWqWPIGuVSqzUkV3Cris5JrlTRQ==", + "dependencies": { + "deffy": "^2.2.2", + "typpy": "^2.3.4" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" + }, + "node_modules/unzip-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", + "integrity": "sha512-pwCcjjhEcpW45JZIySExBHYv5Y9EeL2OIGEfrSKp2dMUFGFv4CpvZkwJbVge8OvGH2BNNtJBx67DuKuJhf+N5Q==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==", + "dependencies": { + "prepend-http": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/usehooks-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz", + "integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==", + "dependencies": { + "lodash.debounce": "^4.0.8" + }, + "engines": { + "node": ">=16.15.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==" + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w-json": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/w-json/-/w-json-1.3.10.tgz", + "integrity": "sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw==" + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "engines": { + "node": ">=10.4" + } + }, + "node_modules/webpack": { + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-manifest-plugin": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", + "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", + "dependencies": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "webpack": "^4.44.2 || ^5.47.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "dependencies": { + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-background-sync": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", + "integrity": "sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.6.0.tgz", + "integrity": "sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-build": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.6.0.tgz", + "integrity": "sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.11.1", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^11.2.1", + "@rollup/plugin-replace": "^2.4.1", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "rollup-plugin-terser": "^7.0.0", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "6.6.0", + "workbox-broadcast-update": "6.6.0", + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-google-analytics": "6.6.0", + "workbox-navigation-preload": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-range-requests": "6.6.0", + "workbox-recipes": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0", + "workbox-streams": "6.6.0", + "workbox-sw": "6.6.0", + "workbox-window": "6.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.6.0.tgz", + "integrity": "sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==", + "deprecated": "workbox-background-sync@6.6.0", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-core": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz", + "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==" + }, + "node_modules/workbox-expiration": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.6.0.tgz", + "integrity": "sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.6.0.tgz", + "integrity": "sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==", + "deprecated": "It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained", + "dependencies": { + "workbox-background-sync": "6.6.0", + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.6.0.tgz", + "integrity": "sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-precaching": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.6.0.tgz", + "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", + "dependencies": { + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.6.0.tgz", + "integrity": "sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-recipes": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.6.0.tgz", + "integrity": "sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==", + "dependencies": { + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-routing": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz", + "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-strategies": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz", + "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-streams": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.6.0.tgz", + "integrity": "sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==", + "dependencies": { + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0" + } + }, + "node_modules/workbox-sw": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.6.0.tgz", + "integrity": "sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==" + }, + "node_modules/workbox-webpack-plugin": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.0.tgz", + "integrity": "sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==", + "dependencies": { + "fast-json-stable-stringify": "^2.1.0", + "pretty-bytes": "^5.4.1", + "upath": "^1.2.0", + "webpack-sources": "^1.4.3", + "workbox-build": "6.6.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": "^4.4.0 || ^5.9.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/workbox-window": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.6.0.tgz", + "integrity": "sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "6.6.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/package.json b/src/Strata.ContinuousImprovement.Web/continuousimprovement/package.json new file mode 100644 index 0000000..447cf30 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/package.json @@ -0,0 +1,96 @@ +{ + "name": "continuousimprovement", + "version": "0.1.0", + "private": true, + "homepage": "/continuousimprovement", + "dependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@strata/charts": "^0.2.0", + "@strata/core": "^5.3.0", + "@strata/eslint-config": "^3.6.0-1", + "@strata/jazz": "^0.6.0", + "@strata/logging": "^0.2.0", + "@strata/navbar": "^4.27.0", + "@strata/score": "^2.4.3", + "@strata/signalr": "^0.8.0", + "@strata/tempo": "^6.33.0", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^11.2.7", + "@types/jest": "^27.5.2", + "@types/node": "^14.18.36", + "@types/react": "^17.0.53", + "@types/react-dom": "^17.0.19", + "@types/react-router": "^5.1.20", + "@types/react-router-dom": "^5.3.3", + "@types/react-test-renderer": "^17.0.2", + "@typescript-eslint/eslint-plugin": "^4.33.0", + "chart.js": "^2.9.4", + "dayjs": "^1.11.9", + "eslint": "^7.32.0", + "eslint-plugin-flowtype": "^5.10.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^4.6.0", + "lodash": "^4.17.21", + "lodash.debounce": "^4.0.8", + "pkg.json": "^2.0.8", + "prettier": "^2.8.4", + "react": "^17.0.1", + "react-app-polyfill": "^2.0.0", + "react-chartjs-2": "^2.11.1", + "react-dom": "^17.0.1", + "react-router": "^5.3.4", + "react-router-dom": "^5.3.4", + "react-scripts": "^5.0.1", + "react-test-renderer": "^17.0.1", + "react-to-print": "^2.14.12", + "sass": "^1.64.1", + "source-map-explorer": "^2.5.3", + "typescript": "^4.9.5", + "usehooks-ts": "^3.1.1" + }, + "scripts": { + "analyze": "source-map-explorer 'build/static/js/*.js'", + "serve": "npx serve -s build", + "start": "react-scripts --max_old_space_size=8192 start", + "build": "react-scripts --max_old_space_size=8192 build", + "test": "react-scripts test", + "test-ci": "react-scripts test --watchAll=false", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "@strata" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all", + "ie 11" + ], + "development": [ + ">0.2%", + "not dead", + "not op_mini all", + "ie 11" + ] + }, + "jest": { + "collectCoverageFrom": [ + "**/*.{ts,tsx}", + "!**/coverage/**", + "!**/node_modules/**", + "!**/babel.config.js", + "!**/jest/Setup.js" + ] + }, + "devDependencies": { + "@strata/test-utils": "^2.0.0", + "@types/lodash.debounce": "^4.0.7", + "ajv": "^8.16.0", + "ajv-keywords": "^5.1.0", + "schema-utils": "^4.2.0" + } +} \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/public/config.js b/src/Strata.ContinuousImprovement.Web/continuousimprovement/public/config.js new file mode 100644 index 0000000..2ba300f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/public/config.js @@ -0,0 +1,16 @@ +window['config'] = { + appName: 'continuousimprovement', + apiUrl: 'https://localhost:8443/api/', + //apiUrl: 'https://continuousimprovement-api.dev.stratanetwork.net/api/', + analyticsApi: 'https://analytics-api.dev.stratanetwork.net/api/', + identityServerUrl: 'https://identity.dev.stratanetwork.net/', + menuUrl: 'https://menu.dev.stratanetwork.net/api/', + env: 'local', + jazzUrl: 'https://dev.sdt.local/StrataJazz', + scoreUrl: 'https://schema-api.dev.stratanetwork.net/api/v2/', + walkMeScriptUrl: 'https://cdn.walkme.com/users/5d201c99fc674ba1a85ac2680bfc4bfa/test/walkme_5d201c99fc674ba1a85ac2680bfc4bfa_https.js', + helpCenterUrl: 'https://stratadecisiontechnology.mcoutput.com/stratajazz/', + analyticsUrl: 'https://analytics.dev.stratanetwork.net/analytics/', + hangfireUrl: 'https://localhost:44369', + contractsUrl: 'https://contracts.dev.stratanetwork.net/contracts/' +}; \ No newline at end of file diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/public/manifest.json b/src/Strata.ContinuousImprovement.Web/continuousimprovement/public/manifest.json new file mode 100644 index 0000000..1f2f141 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/public/robots.txt b/src/Strata.ContinuousImprovement.Web/continuousimprovement/public/robots.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/App.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/App.tsx new file mode 100644 index 0000000..67c3a31 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/App.tsx @@ -0,0 +1,51 @@ +import { initChartDefaults } from '@strata/charts/lib'; +import { AuthProvider } from '@strata/core/lib'; +import { RouteLogger } from '@strata/logging/lib/RouteLogger'; +import ErrorBoundary from '@strata/tempo/lib/errorboundary'; +import PageLoader from '@strata/tempo/lib/pageloader'; +import TempoProvider from '@strata/tempo/lib/tempoprovider/TempoProvider'; +import Unload from '@strata/tempo/lib/unload'; +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import { ConfigurationsProvider } from './shared/configuration/ConfigurationContext'; +import { FeatureFlagProvider } from './shared/featureFlag/FeatureFlagContext'; +import Navigation from './shared/Navigation'; +import NotificationHub from './shared/notification/NotificationHub'; +import NotificationNotifier from './shared/notification/NotificationNotifier'; +import { SystemSettingsProvider } from './shared/systemSettings/SystemSettingsContext'; +import { UserSettingsProvider } from './shared/userSetting/UserSettingContext'; + +const App: React.FC = () => { + initChartDefaults(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default App; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/AppConfig.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/AppConfig.ts new file mode 100644 index 0000000..d0fce74 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/AppConfig.ts @@ -0,0 +1,12 @@ +declare global { + interface Window { + config: any; + } +} + +const appSettings = { + API_URL: window.config.API_URL, + ENV: window.config.ENV +}; + +export default appSettings; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/ChargeCode.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/ChargeCode.tsx new file mode 100644 index 0000000..cbf7496 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/ChargeCode.tsx @@ -0,0 +1,28 @@ +import React, { useEffect, useState } from 'react'; +import Opportunities from './opportunities/Opportunities'; +import OpportunitiesHeader from '../shared/opportunities/OpportunitiesHeader'; +import { useFeatureFlags } from '../shared/featureFlag/FeatureFlagContext'; + +const ChargeCode: React.FC = () => { + const [chargeCodeBeta, setChargeCodeBeta] = useState(false); + + const featureFlags = useFeatureFlags(); + + useEffect(() => { + if (featureFlags.isEnabled) { + const beta = featureFlags.isEnabled('chargecodebeta'); + setChargeCodeBeta(beta); + } + }, [featureFlags]); + + return chargeCodeBeta ? ( + <> + + + + ) : ( + <> + ); +}; + +export default ChargeCode; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/ChargeCodeDetail.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/ChargeCodeDetail.tsx new file mode 100644 index 0000000..84065b9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/ChargeCodeDetail.tsx @@ -0,0 +1,251 @@ +import ScoreMemberPicker from '@strata/score/lib/ScoreMemberPicker/ScoreMemberPicker'; +import { Button } from '@strata/tempo/lib'; +import DropDown from '@strata/tempo/lib/dropdown/DropDown'; +import Form from '@strata/tempo/lib/form/Form'; +import UploadIcon from '@strata/tempo/lib/icon/UploadIcon'; +import RadioGroup from '@strata/tempo/lib/radiogroup/RadioGroup'; +import Spacing from '@strata/tempo/lib/spacing/Spacing'; +import Text from '@strata/tempo/lib/text'; +import React, { useEffect, useState } from 'react'; +import { IEntityInfo, newEntityInfo } from '../../shared/configuration/IEntityInfo'; +import { IServiceLineInfo, newServiceLineInfo } from '../../shared/configuration/IServiceLineInfo'; +import { configurationService } from '../../shared/configuration/configurationService'; +import { EncounterGroup } from '../../shared/opportunities/data/EncounterGroup'; +import { IScoreFieldInfo } from '../../shared/opportunities/data/IScoreFieldInfo'; +import { OpportunityLevelTypes } from '../../shared/opportunities/data/OpportunityLevelTypes'; +import { settingsService } from '../../shared/settingsService'; +import UserSettingContext from '../../shared/userSetting/UserSettingContext'; +import { explorationPopulationIdAttributeGuid, splitCamelCase } from '../../shared/utilities'; + +export interface IChargeCodeDetailProps { + setUploadModalVisible: (visible: boolean) => void; + encounterGroup: number; + opportunityLevel: number; + isSeasonal: boolean; + chargeCodeValidation: (chargeCodes: string[]) => Promise; + opportunityLevelValidation: (opportunityLevel: number, opportunityLevels: string[]) => Promise; + setIsLoading?: (isLoading: boolean) => void; +} +const ChargeCodeDetail: React.FC = (props: IChargeCodeDetailProps) => { + const { setUploadModalVisible, chargeCodeValidation, opportunityLevelValidation, setIsLoading } = props; + const [modalEntityProperties, setModalEntityProperties] = useState(newEntityInfo); + const [modalServiceLineProperties, setModalServiceLineProperties] = useState(newServiceLineInfo); + const [encounterGroup, setEncounterGroup] = useState(props.encounterGroup); + const [opportunityLevel, setOpportunityLevel] = useState(props.opportunityLevel); + const { userSetting } = React.useContext(UserSettingContext); + + useEffect(() => { + const loadData = async (configurationGuid: string) => { + if (setIsLoading) setIsLoading(true); + const entityInfo = await configurationService.getEntityInfo(configurationGuid); + const serviceLineInfo = await configurationService.getServiceLineInfo(configurationGuid); + setModalEntityProperties(entityInfo); + setModalServiceLineProperties(serviceLineInfo); + if (setIsLoading) setIsLoading(false); + }; + if (userSetting.configurationGuid !== '') { + loadData(userSetting.configurationGuid); + } + }, [userSetting.configurationGuid, setIsLoading]); + + const handleOpportunityLevelChange = (e: OpportunityLevelTypes) => { + setOpportunityLevel(e); + }; + + const getOpportunityLevelFieldInfo = (): IScoreFieldInfo => { + switch (opportunityLevel) { + case OpportunityLevelTypes.Entity: + return { + dimensionName: modalEntityProperties.entityLabel, + dimensionGuidOrGlobalId: modalEntityProperties.departmentRollupGlobalId, + modalTitle: modalEntityProperties.entityTitle + }; + case OpportunityLevelTypes.ServiceLine: + return { + dimensionName: modalServiceLineProperties.serviceLineName, + dimensionGuidOrGlobalId: modalServiceLineProperties.serviceLineDimensionGuid, + modalTitle: modalServiceLineProperties.serviceLineTitle + }; + case OpportunityLevelTypes.Specialty: + return { + dimensionName: 'DSS Physician Specialty', + dimensionGuidOrGlobalId: 'DSS Physician Specialty', + modalTitle: 'Physician Specialties' + }; + case OpportunityLevelTypes.Provider: + return { + dimensionName: 'Primary Physicians', + dimensionGuidOrGlobalId: 'Physician Primary', + modalTitle: 'Primary Physicians' + }; + default: + return { + dimensionName: '', + dimensionGuidOrGlobalId: '', + modalTitle: '' + }; + } + }; + + const getEncounterGroupFieldInfo = (): IScoreFieldInfo => { + if (encounterGroup === EncounterGroup.PatientPopulation) { + return { + dimensionName: 'Patient Populations', + dimensionGuidOrGlobalId: 'Clinical Indicator', + modalTitle: 'Patient Populations' + }; + } else { + return { + dimensionName: 'Case Type', + dimensionGuidOrGlobalId: 'Case Type', + hierarchyGuid: 'c20b06b1-5a60-4fa5-825d-8e9a0524f783', + filters: [{ op: 'Equals', attributeGuid: explorationPopulationIdAttributeGuid, value: 0 }], + modalTitle: 'Case Types' + }; + } + }; + + const opportunityLevelFieldInfo = getOpportunityLevelFieldInfo(); + const encounterGroupFieldInfo = getEncounterGroupFieldInfo(); + + const chargeCodeLabel = ( + <> + + + Charge Codes + + + + + ); + + return ( + <> + + + setEncounterGroup(e.target.value as EncounterGroup)} + options={[ + { value: EncounterGroup.CaseType, label: 'Case Type' }, + { value: EncounterGroup.PatientPopulation, label: 'Patient Population' } + ]} + /> + + ({ + async validator(_, value) { + if (!value) { + return Promise.reject(); + } + if (value.length > 20) { + return Promise.reject(`You have exceeded the maximum of 20 ${splitCamelCase(EncounterGroup[encounterGroup])} selections.`); + } + return Promise.resolve(); + } + }) + ]} + > + + + ({ + async validator(_, value) { + if (!value) { + return Promise.reject(); + } + if (!(await chargeCodeValidation(value))) { + return Promise.reject(`You have exceeded the maximum of ${await settingsService.getChargeCodeLimit()} charge code selections. Expand the rollups to narrow down your selections.`); + } + return Promise.resolve(); + } + }) + ]} + > + + + + handleOpportunityLevelChange(e as OpportunityLevelTypes)} + placeholder='Opportunity Level' + items={[ + { value: OpportunityLevelTypes.Organization, text: 'System' }, + { value: OpportunityLevelTypes.Entity, text: `${modalEntityProperties.entityText}` }, + { value: OpportunityLevelTypes.ServiceLine, text: `${modalServiceLineProperties.serviceLineName}` }, + { value: OpportunityLevelTypes.Specialty, text: 'Specialty' }, + { value: OpportunityLevelTypes.Provider, text: 'Physician' } + ]} + /> + + {opportunityLevel !== undefined && opportunityLevel !== OpportunityLevelTypes.Organization && opportunityLevelFieldInfo.dimensionName !== '' && ( + <> + ({ + async validator(_, value) { + if (!value) { + return Promise.reject(); + } + if (!(await opportunityLevelValidation(opportunityLevel, value))) { + return Promise.reject( + `You have exceeded the maximum of ${await settingsService.getOpportunityLevelLimit()} ${splitCamelCase( + OpportunityLevelTypes[opportunityLevel] + )} selections. Expand the rollups to narrow down your selections.` + ); + } + return Promise.resolve(); + } + }) + ]} + > + + + + )} + + + + + + ); +}; + +export default ChargeCodeDetail; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/Initiative.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/Initiative.tsx new file mode 100644 index 0000000..4d44bf7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/Initiative.tsx @@ -0,0 +1,284 @@ +import { useHelpCenter } from '@strata/core/lib'; +import Button from '@strata/tempo/lib/button'; +import Form from '@strata/tempo/lib/form'; +import Header from '@strata/tempo/lib/header/Header'; +import InfoCircleIcon from '@strata/tempo/lib/icon/InfoCircleIcon'; +import useToast from '@strata/tempo/lib/toast'; +import Wizard from '@strata/tempo/lib/wizard'; +import dayjs from 'dayjs'; +import isBetween from 'dayjs/plugin/isBetween'; +import React, { useContext, useEffect, useState } from 'react'; +import { useHistory } from 'react-router'; +import { OpportunityTypes } from '../../opportunities/opportunities/data/IAllOpportunity'; +import { useConfigurations } from '../../shared/configuration/ConfigurationContext'; +import InitiativeDetail from '../../shared/initiatives/InitiativeDetail'; +import InitiativeMilestones from '../../shared/initiatives/InitiativeMilestones'; +import InitiativeRoles from '../../shared/initiatives/InitiativeRoles'; +import { IInitiative } from '../../shared/initiatives/data/IInitiative'; +import { IInitiativeProps } from '../../shared/initiatives/data/IInitiativeProps'; +import { newOrgDefinedInitiative } from '../../shared/initiatives/data/IOrgDefinedInitiative'; +import { IUserProfile } from '../../shared/initiatives/data/IUserProfile'; +import { IWorkstream } from '../../shared/initiatives/data/IWorkstream'; +import { initiativeService } from '../../shared/initiatives/data/initiativeService'; +import notificationHub from '../../shared/notification/NotificationHub'; +import { nameValidationService } from '../../shared/opportunities/data/nameValidationService'; +import UserSettingContext from '../../shared/userSetting/UserSettingContext'; +import { usersService } from '../../shared/users/usersService'; +import ChargeCodeUploadModal from '../opportunities/ChargeCodeUploadModal'; +import { IChargeCodeOpportunitySaveModel, newChargeCodeOpportunitySaveModel } from '../opportunities/data/IChargeCodeOpportunitySaveModel'; +import { opportunityService } from '../opportunities/data/opportunityService'; +import ChargeCodeDetail from './ChargeCodeDetail'; +import { IChargeCodeInitiative, newChargeCodeInitiative } from './data/IChargeCodeInitiative'; +import { cleanUpChargeCodeInitiative } from './data/IChargeCodeInitiativeDto'; +import { chargeCodeInitiativeService } from './data/chargeCodeInitiativeService'; +import { RegEx } from '../../shared/validation/RegEx'; + +const Initiative: React.FC = () => { + const { currentConfiguration } = useConfigurations(); + const [opportunity, setOpportunity] = useState(newChargeCodeOpportunitySaveModel('')); + const [initiative, setInitiative] = useState(newOrgDefinedInitiative(currentConfiguration)); + const [owners, setOwners] = React.useState([]); + const [costLeaders, setCostLeaders] = React.useState([]); + const [executiveSponsors, setExecutiveSponsors] = React.useState([]); + const [workstreams, setWorkstreams] = React.useState([]); + const [form] = Form.useForm(); + const [stepNumber, setStepNumber] = useState(0); + const [loading, setLoading] = useState(false); + const [milestoneIdx, setMilestoneIdx] = useState(0); + const [uploadModalVisible, setUploadModalVisible] = useState(false); + const { userSetting } = useContext(UserSettingContext); + const history = useHistory(); + const hub = notificationHub.useConnection(); + const toast = useToast(); + + const { networkOpportunityName, networkOpportunityId, opportunityGuid } = history.location.state as IInitiativeProps; + + dayjs.extend(isBetween); + + useEffect(() => { + const loadData = async () => { + setLoading(true); + if (!opportunityGuid) { + // create + const newInitiative = newChargeCodeInitiative(currentConfiguration); + if (networkOpportunityName) { + newInitiative.name = networkOpportunityName; + } + setInitiative(newInitiative); + form.setFieldsValue(newInitiative); + } else { + // edit + const editOpportunity = await opportunityService.getEditModel(opportunityGuid); + setOpportunity(editOpportunity); + form.setFieldsValue({ ...form, ...editOpportunity }); + if (opportunityService.getInitiativeInfo) { + const editInitiative = await opportunityService.getInitiativeInfo(opportunityGuid); + editInitiative.startTracking = dayjs(editInitiative.trackingStart); + setInitiative(editInitiative); + form.setFieldsValue({ ...form, ...editOpportunity, ...editInitiative }); + } + setLoading(false); + } + const owners = await usersService.getOwners(); + const costLeaders = await usersService.getCostLeaders(); + const execSponsors = await usersService.getExecutiveSponsors(); + const workstreams = await initiativeService.getWorkstreams(); + setOwners(owners); + setCostLeaders(costLeaders); + setExecutiveSponsors(execSponsors); + setWorkstreams(workstreams); + }; + if (currentConfiguration) { + loadData(); + } + setLoading(false); + }, [currentConfiguration, networkOpportunityName, form, opportunityGuid]); + + const helpCenter = useHelpCenter(); + + const handleWizardChange = async (currentStep: number, newStep: number) => { + form.submit(); + const formValues = form.getFieldsValue(); + + if (newStep < currentStep) { + setStepNumber(newStep); + return; + } else { + switch (currentStep) { + case 0: + if (formValues.name.trim() === '' || !RegEx.JazzRestrictedCharacters.test(formValues.name) || !(await validateName(formValues.name)) || !formValues.startTracking) { + return; + } + break; + case 1: + if ( + formValues.encounterGroup === undefined || + formValues.encounterGroups === undefined || + formValues.encounterGroups.length === 0 || + formValues.chargeCodes === undefined || + formValues.chargeCodes.length === 0 || + !(await validateChargeCodes(formValues.chargeCodes)) || + formValues.opportunityLevel === undefined || + (formValues.opportunityLevel > 0 && (formValues.opportunityLevels === undefined || formValues.opportunityLevels.length === 0)) || + !(await validateOpportunityLevels(formValues.opportunityLevel, formValues.opportunityLevels)) + ) { + return; + } + break; + case 2: + if ( + formValues.costLeader === '' || + formValues.costLeader === undefined || + formValues.executiveSponsor === '' || + formValues.executiveSponsor === undefined || + formValues.owner === '' || + formValues.owner === undefined + ) { + return; + } + break; + } + } + + setStepNumber(newStep); + }; + + const handleCancel = () => { + history.goBack(); + }; + + const handleSave = async () => { + form.submit(); // trigger any validation + setLoading(true); + try { + const detailValues = form.getFieldsValue(); + const cleanedInitiative = cleanUpChargeCodeInitiative({ ...opportunity, ...initiative, ...detailValues }); + const data = newChargeCodeInitiative(currentConfiguration, cleanedInitiative); + notificationHub.register( + hub, + async () => { + if (!opportunityGuid) { + await chargeCodeInitiativeService.create(data, networkOpportunityId); + } else { + await chargeCodeInitiativeService.update(data, networkOpportunityId); + } + }, + cleanedInitiative.name + ); + } catch (error) { + // Handles exceptions or unhandled validation from server + toast.show({ + key: opportunity.name, + message: `${opportunity.name} Opportunity was not saved. Try again and contact us if the issue continues.`, + toastType: 'error' + }); + } + }; + + const handleFormValuesChanged = (changedValues: any) => { + const changedInitiative = { ...opportunity, ...initiative, ...changedValues }; + + // if trackingStart changed, update startTracking + if (changedValues.startTracking != null) { + const startTrackingDate: dayjs.Dayjs = dayjs(changedValues.startTracking); + changedInitiative.trackingStart = startTrackingDate.format('YYYYMMDD'); + } + + // if encounterGroup changed, we have to reset the selected encounterGroups + if (changedValues.encounterGroup != null) { + changedInitiative.encounterGroups = []; + form.resetFields(['encounterGroups']); + form.setFieldValue('encounterGroups', changedValues.encounterGroups); + } + + if (changedValues.opportunityLevel != null) { + changedInitiative.opportunityLevels = []; + form.resetFields(['opportunityLevels']); + form.setFieldValue('opportunityLevels', changedValues.opportunityLevels); + } + + setOpportunity(changedInitiative); + setInitiative(changedInitiative); + }; + + const handleChargeCodesUploaded = (chargeCodes: string[]) => { + const changedInitiative = { ...initiative, ...{ chargeCodes: chargeCodes } } as IChargeCodeInitiative; + setInitiative(changedInitiative); + form.setFieldsValue(changedInitiative); + setUploadModalVisible(false); + }; + + const validateName = async (name: string) => { + return await nameValidationService.nameIsValid(userSetting.configurationGuid, OpportunityTypes.ChargeCode, name, opportunityGuid); + }; + + const validateChargeCodes = async (chargeCodes: string[]) => { + setLoading(true); + const valid = await opportunityService.validateChargeCodes(chargeCodes); + setLoading(false); + return valid; + }; + + const validateOpportunityLevels = async (opportunityLevel: number, opportunityLevels: string[]) => { + setLoading(true); + const valid = await opportunityService.validateOpportunityLevels(opportunityLevel, opportunityLevels, currentConfiguration?.configurationGuid); + setLoading(false); + return valid; + }; + + const wizardSteps = () => { + const startMonth = dayjs(currentConfiguration?.orgDefinedTrackingStartDate); + const endMonth = dayjs(currentConfiguration?.orgDefinedTrackingEndDate); + const steps: JSX.Element[] = [ + + + + ]; + steps.push( + + + + ); + steps.push( + + + + ); + steps.push( + + + + ); + return steps; + }; + + return ( + <> +
} onClick={() => helpCenter.openHelpPage('1440')} />} + /> +
+ + {wizardSteps()} + +
+ setUploadModalVisible(false)} + onOk={handleChargeCodesUploaded} + /> + + ); +}; +export default Initiative; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/data/IChargeCodeInitiative.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/data/IChargeCodeInitiative.ts new file mode 100644 index 0000000..b4b4017 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/data/IChargeCodeInitiative.ts @@ -0,0 +1,24 @@ +import { IOrgDefinedInitiative, newOrgDefinedInitiative } from '../../../shared/initiatives/data/IOrgDefinedInitiative'; +import { EncounterGroup } from '../../../shared/opportunities/data/EncounterGroup'; +import { OpportunityLevelTypes } from '../../../shared/opportunities/data/OpportunityLevelTypes'; +import { IConfiguration } from '../../../shared/configuration/IConfiguration'; + +export interface IChargeCodeInitiative extends IOrgDefinedInitiative { + encounterGroup?: EncounterGroup; + opportunityLevel?: OpportunityLevelTypes | undefined; + encounterGroups?: []; + opportunityLevels?: []; + chargeCodes: string[]; + isSeasonal: boolean; +} + +export const newChargeCodeInitiative = (configuration: IConfiguration | undefined, data: Partial = {}): IChargeCodeInitiative => { + const initiative = newOrgDefinedInitiative(configuration, data) as IChargeCodeInitiative; + initiative.costDriver = 'Charge Code'; + initiative.encounterGroup = data.encounterGroup ?? EncounterGroup.CaseType; + initiative.opportunityLevel = data.opportunityLevel ?? OpportunityLevelTypes.Organization; + initiative.encounterGroups = data.encounterGroups ?? []; + initiative.chargeCodes = data.chargeCodes ?? []; + initiative.isSeasonal = data.isSeasonal ?? false; + return initiative; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/data/IChargeCodeInitiativeDto.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/data/IChargeCodeInitiativeDto.ts new file mode 100644 index 0000000..70b27c0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/data/IChargeCodeInitiativeDto.ts @@ -0,0 +1,35 @@ +import { cleanUpOrgDefinedInitiative } from '../../../shared/initiatives/data/IOrgDefinedInitiative'; +import { IOrgDefinedInitiativeDto, newOrgDefinedInitiativeDto } from '../../../shared/initiatives/data/IOrgDefinedInitiativeDto'; +import { EncounterGroup } from '../../../shared/opportunities/data/EncounterGroup'; +import { OpportunityLevelTypes } from '../../../shared/opportunities/data/OpportunityLevelTypes'; +import { IChargeCodeInitiative } from './IChargeCodeInitiative'; + +export interface IChargeCodeInitiativeDto extends IOrgDefinedInitiativeDto { + configurationGuid: string; + encounterGroup: EncounterGroup; + opportunityLevel: OpportunityLevelTypes | undefined; + encounterGroups: []; + opportunityLevels: []; + chargeCodes: string[]; + isSeasonal: boolean; +} + +export const newChargeCodeInitiativeDto = (data: Partial = {}): IChargeCodeInitiativeDto => { + const initiative = newOrgDefinedInitiativeDto(data) as IChargeCodeInitiativeDto; + initiative.encounterGroup = data.encounterGroup ?? EncounterGroup.CaseType; + initiative.encounterGroups = data.encounterGroups ?? []; + initiative.chargeCodes = data.chargeCodes ?? []; + initiative.opportunityLevel = data.opportunityLevel ?? OpportunityLevelTypes.Organization; + initiative.opportunityLevels = data.opportunityLevels ?? []; + initiative.isSeasonal = data.isSeasonal ?? false; + return initiative; +}; + +export const cleanUpChargeCodeInitiative = (data: Partial): IChargeCodeInitiativeDto => { + try { + return newChargeCodeInitiativeDto({ ...cleanUpOrgDefinedInitiative({ ...data }), ...data }); + } catch (error) { + alert(error); + return newChargeCodeInitiativeDto({ ...data }); + } +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/data/chargeCodeInitiativeService.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/data/chargeCodeInitiativeService.ts new file mode 100644 index 0000000..570f779 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/initiatives/data/chargeCodeInitiativeService.ts @@ -0,0 +1,25 @@ +import { appConfig } from '@strata/core/lib/config/appConfig'; +import { getSecureService } from '@strata/core/lib/http/getSecureService'; +import { IInitiativeDto } from '../../../shared/initiatives/data/IInitiativeDto'; +import { IChargeCodeInitiative } from './IChargeCodeInitiative'; + +const { httpPost, httpGet } = getSecureService(`${appConfig.apiUrl}v1/`); +const url = 'initiatives'; + +export const chargeCodeInitiativeService = { + create: function (initiative: IChargeCodeInitiative, networkOpportunityId?: number): Promise { + return httpPost(`${url}/create-charge-code`, { + initiative, + networkOpportunityId + }); + }, + update: function (initiative: IInitiativeDto, networkOpportunityId?: number): Promise { + return httpPost(`${url}/update-charge-code`, { + initiative, + networkOpportunityId + }); + }, + finalize: async (initiativeGuid: string, callback: () => void): Promise => { + return httpGet(`${url}/${initiativeGuid}/finalize`).then(callback); + } +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/ChargeCodeOpportunityDetail.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/ChargeCodeOpportunityDetail.tsx new file mode 100644 index 0000000..5c7ad75 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/ChargeCodeOpportunityDetail.tsx @@ -0,0 +1,533 @@ +import { useJazzUrlWithVersion } from '@strata/core/lib'; +import { ICellArgs, ICellEditorArgs, TreeUtils, usePageLoader, useToast } from '@strata/tempo/lib'; +import Button from '@strata/tempo/lib/button'; +import Card from '@strata/tempo/lib/card'; +import { DataHeaderCell, DataHeaderGroup, DataHeaderRow } from '@strata/tempo/lib/dataheader'; +import Divider from '@strata/tempo/lib/divider'; +import LayoutGrid from '@strata/tempo/lib/layoutgrid'; +import { useModal } from '@strata/tempo/lib/modal'; +import TreeGrid from '@strata/tempo/lib/treegrid'; +import debounce from 'lodash/debounce'; +import sumBy from 'lodash/sumBy'; +import React, { useEffect, useMemo, useState, useCallback } from 'react'; +import { useHistory, useParams } from 'react-router'; +import { OpportunityTypes } from '../../opportunities/opportunities/data/IAllOpportunity'; +import { useConfigurations } from '../../shared/configuration/ConfigurationContext'; +import InitiativeCreateModal from '../../shared/initiatives/InitiativeCreateModal'; +import { IInitiative, getGuid, getInitiative, newInitiative } from '../../shared/initiatives/data/IInitiative'; +import { cleanUpInitiative } from '../../shared/initiatives/data/IInitiativeDto'; +import notificationHub from '../../shared/notification/NotificationHub'; +import NotesModal from '../../shared/opportunities/NotesModal'; +import OpportunityDetail from '../../shared/opportunities/OpportunityDetail'; +import OpportunityDetailHeader from '../../shared/opportunities/OpportunityDetailHeader'; +import { IOpportunity } from '../../shared/opportunities/data/IOpportunity'; +import { securityService } from '../../shared/security/data/securityService'; +import { newChargeCodeInitiative } from '../initiatives/data/IChargeCodeInitiative'; +import { chargeCodeInitiativeService } from '../initiatives/data/chargeCodeInitiativeService'; +import OpportunityDetailsCard from '../../shared/opportunities/OpportunityDetailsCard'; +import ChargeCodeOpportunityEditModal from './ChargeCodeOpportunityEditModal'; +import { IChargeCodeOpportunity } from './data/IChargeCodeOpportunity'; +import { IChargeCodeOpportunitySaveModel } from './data/IChargeCodeOpportunitySaveModel'; +import { opportunityService } from './data/opportunityService'; +import { TREE_DATA_LEVEL_SEPARATOR } from '../../shared/constants'; +import { DefaultChargeCodeOpportunityDetailChanges, IChargeCodeOpportunityDetailChanges } from './data/IChargeCodeOpportunityDetailChanges'; + +const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +const reTreeDataLevelSeparator = new RegExp(escapeRegExp(TREE_DATA_LEVEL_SEPARATOR), 'g'); +const validationLevel = 3; + +const ChargeCodeOpportunityDetail: React.FC = () => { + const { opportunityGuid } = useParams<{ opportunityGuid: string }>(); + const gridRef = React.useRef(null); + const changedValues = React.useRef<{ [key: string]: IChargeCodeOpportunityDetailChanges }>({}); + const [opportunityId, setOpportunityId] = useState(0); + const [gridLoading, setGridLoading] = useState(false); + const [opportunityDetailData, setOpportunityDetailData] = useState(); + const [initiativeModalVisible, setInitiativeModalVisible] = useState(false); + const [initiativeData, setInitiativeData] = useState(newInitiative); + const [hasInitiative, setHasInitiative] = useState(false); + const [startMonth, setStartMonth] = useState(''); + const [endMonth, setEndMonth] = useState(''); + const jazzUrl = useJazzUrlWithVersion(); + const [isLocked, setIsLocked] = useState(false); + const [notesModalVisible, setNotesModalVisible] = useState(false); + const [chargeCodeEditModalVisible, setChargeCodeEditModalVisible] = useState(false); + const [isCardVisible, setCardVisible] = useState(false); + const [isReadable, setIsReadable] = useState(true); + const [isEditable, setIsEditable] = useState(false); + const [isSecurityEditable, setIsSecurityEditable] = useState(false); + const hub = notificationHub.useConnection(); + + const { setLoading } = usePageLoader(); + const modal = useModal(); + const toast = useToast(); + const history = useHistory(); + const { currentConfiguration } = useConfigurations(); + + useEffect(() => { + // prevent users w/o read access page access + if (!isReadable) { + history.push('/'); + } + }, [isReadable, history]); + + useEffect(() => { + gridRef?.current?.validateGrid(); + changedValues.current = {}; + }, [opportunityDetailData]); + + useEffect(() => { + const setUserPermissions = async () => { + setIsReadable(await securityService.getGeneralAccessPermissions()); + const userPermissions = await securityService.getUserPermissions(opportunityGuid); + setIsEditable(userPermissions.canWrite); + setIsSecurityEditable(userPermissions.canSecure); + }; + + const fetchData = async () => { + setLoading(true); + setCardVisible(false); + const opportunityDetail = await opportunityService.getById(opportunityGuid); + setOpportunityDetailData(opportunityDetail); + setOpportunityId(opportunityDetail.opportunityId); + if (opportunityService.getInitiativeInfo) { + const info = await opportunityService.getInitiativeInfo(opportunityGuid); + setInitiativeData(info); + setHasInitiative(info.trackingStart !== null && info.trackingStart !== ''); + setStartMonth(info.startMonth); + setEndMonth(info.endMonth); + setIsLocked(info.isLocked); + } + setCardVisible(true); + setLoading(false); + }; + + setUserPermissions(); + fetchData(); + }, [opportunityGuid, setLoading]); + + const handleHideOrShow = () => { + const updateHidden = async () => { + if (opportunityDetailData === undefined) { + return; + } + await opportunityService.setHidden(opportunityGuid, !opportunityDetailData.isHidden); + setOpportunityDetailData({ ...opportunityDetailData, ...{ isHidden: !opportunityDetailData.isHidden } }); + }; + updateHidden(); + }; + + const handleSaveNotes = async (note: string) => { + if (opportunityDetailData !== undefined) { + await opportunityService.setNote(opportunityGuid, note); + } + setOpportunityDetailData({ ...opportunityDetailData, ...{ note: note } } as IChargeCodeOpportunity); + setNotesModalVisible(false); + }; + + const handleDelete = async (callback: () => void, failed: (err: any) => void) => { + await opportunityService.delete(opportunityGuid, callback, failed); + }; + + const handleUpdate = async (updatedOpportunity: IChargeCodeOpportunitySaveModel) => { + modal.confirm({ + title: `Update Opportunity?`, + content: `Updating will reset this opportunity's goals.`, + okText: 'Update', + cancelText: 'Cancel', + async onOk() { + notificationHub.register( + hub, + async () => { + setGridLoading(true); + setChargeCodeEditModalVisible(false); + const result = await opportunityService.updateChargeCodeOpportunity(updatedOpportunity); + // notifier logic redirects page if result == true + if (!result) { + setGridLoading(false); + } + }, + updatedOpportunity.name + ); + toast.show({ + key: updatedOpportunity.name, + toastType: 'loading', + message: `Updating ${updatedOpportunity.name} Opportunity`, + duration: 30 + }); + } + }); + }; + + const goalErrorModal = () => { + modal.alert({ + title: 'Goal Failed to Update', + content: 'An unknown error occurred. Try editing the goal again and contact us if the issue continues.', + alertType: 'error' + }); + }; + + const updateGoals = useCallback(async () => { + if (!changedValues.current) return; + + setGridLoading(true); + + try { + const changes = Object.values(changedValues.current); + const result = await opportunityService.updateGoals(opportunityGuid, changes); + + if (!result) { + goalErrorModal(); + } + + const opportunityDetail = await opportunityService.getById(opportunityGuid); + setOpportunityDetailData(opportunityDetail); + } finally { + setGridLoading(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [opportunityGuid, changedValues]); + + const onCellEdit = (args: ICellEditorArgs, newCellValue: number) => { + if (!changedValues.current[args.rowKey]) { + changedValues.current[args.rowKey] = { ...DefaultChargeCodeOpportunityDetailChanges, key: args.rowKey }; + } + switch (args.field) { + case 'goalTotalUnitsOfService': + changedValues.current[args.rowKey].goalTotalUnitsOfService = newCellValue || 0; + break; + case 'goalCostPerUnit': + changedValues.current[args.rowKey].goalCostPerUnit = newCellValue || 0; + break; + } + updateGoalWithDebounce(); + }; + + const updateGoalWithDebounce = useMemo( + () => + debounce(() => { + gridRef?.current?.validateGrid().then((invalidCells) => { + if (!(invalidCells && invalidCells.some((x) => (x.rowKey.match(reTreeDataLevelSeparator) || []).length >= validationLevel - 1))) { + updateGoals(); + } + }); + }, 1000), + [updateGoals] + ); + useEffect(() => { + return () => { + updateGoalWithDebounce.cancel(); + }; + }, [updateGoalWithDebounce]); + + const isCellEditable = (args: ICellArgs) => { + if (isLocked || !isEditable) { + return false; + } else { + return args.row.children == null || args.row.children.length === 0; + } + }; + + const getLeafSum = (args: ICellArgs) => { + const node = args.row; + if (node.children && node.children.length > 0) { + const leafData = TreeUtils.flattenLeafOnly(node.children).map((leafNode) => leafNode.data); + return sumBy(leafData, args.field); + } else { + return node.data[args.field]; + } + }; + + const hideRollupCaseValue = (args: ICellArgs) => { + const node = args.row; + if (node.children && node.children.length > 0 && node.children[0].children && node.children[0].children.length === 0) { + return ''; + } + return node.data[args.field]; + }; + + const getInitiativeUrl = (initiativeGuid: string): string => { + return `${jazzUrl}CCI/ChargeCode/InitiativeViewer.aspx?id=${initiativeGuid}`; + }; + + const getInitiativeInfo = async () => { + if (!hasInitiative) { + setInitiativeModalVisible(true); + } else { + window.location.assign(getInitiativeUrl(initiativeData.initiativeGuid)); + } + }; + + const finalizeInitiative = async () => { + try { + toast.show({ + key: initiativeData.initiativeGuid, + toastType: 'loading', + message: `Finalizing initiative.`, + duration: 0 + }); + + if (opportunityDetailData) { + setLoading(true); + const opportunityDetail = await opportunityService.getById(opportunityGuid); + setOpportunityDetailData(opportunityDetail); + setLoading(false); + } + await chargeCodeInitiativeService.finalize(initiativeData.initiativeGuid, async function () { + toast.show({ + key: initiativeData.initiativeGuid, + toastType: 'success', + message: `Initiative has been finalized and tracking has been run.` + }); + // refresh initiative info now that tracking has been run + if (opportunityService.getInitiativeInfo) { + const info = await opportunityService.getInitiativeInfo(opportunityGuid); + setInitiativeData(info); + setHasInitiative(info.trackingStart !== null && info.trackingStart !== ''); + setStartMonth(info.startMonth); + setEndMonth(info.endMonth); + setIsLocked(info.isLocked); + } + }); + } catch (error) { + if (error instanceof Response) { + // Handles exceptions or unhandled validation from server + toast.show({ + key: initiativeData.initiativeGuid, + message: `An error occurred while finalizing the initiative.`, + toastType: 'error' + }); + } + } + }; + + const handleInitiativeClose = async () => { + setInitiativeModalVisible(false); + }; + + const handleInitiativeOk = async (initiative: IInitiative) => { + setInitiativeModalVisible(false); + initiative.costDriver = 'Charge Code'; + try { + const data = newChargeCodeInitiative(currentConfiguration, cleanUpInitiative({ ...initiative })); + const initiativeGuid = await chargeCodeInitiativeService.create(data); + if (getGuid(initiativeGuid) === undefined) { + toast.show({ toastType: 'error', duration: 0, message: 'Failed to create initiative' }); + return; + } + if (opportunityService.getInitiativeInfo) { + const info = await opportunityService.getInitiativeInfo(opportunityGuid); + setInitiativeData(info); + } + setHasInitiative(true); + toast.show({ + key: initiativeGuid, + toastType: 'success', + duration: 0, + message: ( + <> + {' '} + is created. + + ) + }); + } catch (e) { + alert(e); + } + }; + + return ( + <> + history.push(`/initiatives/edit/charge-code`, { opportunityGuid: opportunityGuid })} + getInitiativeInfo={getInitiativeInfo} + finalizeInitiative={finalizeInitiative} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + > + + + + + + + + + + + + + + + + + + + + + + + + { + setChargeCodeEditModalVisible(true); + }} + /> + + + + setNotesModalVisible(false)} /> + {initiativeModalVisible && ( + + )} + {chargeCodeEditModalVisible && ( + { + setChargeCodeEditModalVisible(false); + }} + onUpdate={(updatedOpportunity: IChargeCodeOpportunitySaveModel) => { + handleUpdate(updatedOpportunity); + }} + /> + )} + + ); +}; + +export default ChargeCodeOpportunityDetail; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/ChargeCodeOpportunityEditModal.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/ChargeCodeOpportunityEditModal.tsx new file mode 100644 index 0000000..ae46cac --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/ChargeCodeOpportunityEditModal.tsx @@ -0,0 +1,303 @@ +import { useHelpCenter } from '@strata/core/lib'; +import { ScoreMemberPicker } from '@strata/score/lib/ScoreMemberPicker'; +import Button from '@strata/tempo/lib/button'; +import DropDown from '@strata/tempo/lib/dropdown'; +import Form from '@strata/tempo/lib/form'; +import InfoCircleIcon from '@strata/tempo/lib/icon/InfoCircleIcon'; +import UploadIcon from '@strata/tempo/lib/icon/UploadIcon'; +import Input from '@strata/tempo/lib/input'; +import Modal from '@strata/tempo/lib/modal'; +import RadioGroup from '@strata/tempo/lib/radiogroup'; +import Spacing from '@strata/tempo/lib/spacing'; +import Text from '@strata/tempo/lib/text'; +import React, { useEffect, useState } from 'react'; +import { OpportunityTypes } from '../../opportunities/opportunities/data/IAllOpportunity'; +import { configurationService } from '../../shared/configuration/configurationService'; +import { IEntityInfo, newEntityInfo } from '../../shared/configuration/IEntityInfo'; +import { IServiceLineInfo, newServiceLineInfo } from '../../shared/configuration/IServiceLineInfo'; +import { EncounterGroup } from '../../shared/opportunities/data/EncounterGroup'; +import { IScoreFieldInfo } from '../../shared/opportunities/data/IScoreFieldInfo'; +import { nameIsDuplicate, nameValidationService } from '../../shared/opportunities/data/nameValidationService'; +import { OpportunityLevelTypes } from '../../shared/opportunities/data/OpportunityLevelTypes'; +import UserSettingContext from '../../shared/userSetting/UserSettingContext'; +import { explorationPopulationIdAttributeGuid } from '../../shared/utilities'; +import { RegEx } from '../../shared/validation/RegEx'; +import ChargeCodeUploadModal from './ChargeCodeUploadModal'; +import { IChargeCodeOpportunitySaveModel } from './data/IChargeCodeOpportunitySaveModel'; +import { opportunityService } from './data/opportunityService'; + +export interface IChargeCodeOpportunityEditModalProps { + isVisible: boolean; + opportunityGuid: string; + onCancel?: () => void; + onUpdate?: (updatedOpportunity: IChargeCodeOpportunitySaveModel) => void; +} + +const ChargeCodeOpportunityEditModal: React.FC = (props: IChargeCodeOpportunityEditModalProps) => { + const { isVisible, opportunityGuid, onCancel, onUpdate } = props; + const [form] = Form.useForm(); + const [opportunity, setOpportunity] = useState(); + const [modalEntityProperties, setModalEntityProperties] = useState(newEntityInfo); + const [modalServiceLineProperties, setModalServiceLineProperties] = useState(newServiceLineInfo); + const [uploadModalVisible, setUploadModalVisible] = useState(false); + + const [initialOpportunity, setInitialOpportunity] = useState(); + const helpCenter = useHelpCenter(); + + const { userSetting } = React.useContext(UserSettingContext); + + useEffect(() => { + const loadData = async (configurationGuid: string) => { + const entityInfo = await configurationService.getEntityInfo(configurationGuid); + const serviceLineInfo = await configurationService.getServiceLineInfo(configurationGuid); + const editModel = await opportunityService.getEditModel(opportunityGuid); + setInitialOpportunity(editModel); + setOpportunity(editModel); + setModalEntityProperties(entityInfo); + setModalServiceLineProperties(serviceLineInfo); + form.setFieldsValue(editModel); + }; + if (userSetting.configurationGuid !== '') { + loadData(userSetting.configurationGuid); + } + }, [userSetting.configurationGuid, opportunityGuid, form]); + + // Save form, clear fields + const handleFormFinish = (values: any) => { + handleOpportunitySave({ ...opportunity, ...values }); + }; + + const handleValuesChanged = (changedValues: any) => { + const mergedOpportunity = { ...opportunity, ...changedValues } as IChargeCodeOpportunitySaveModel; + + if (changedValues.encounterGroup !== undefined && initialOpportunity) { + if (initialOpportunity.encounterGroup !== changedValues.encounterGroup) { + mergedOpportunity.encounterGroups = []; + } else { + mergedOpportunity.encounterGroups = initialOpportunity.encounterGroups; + } + form.resetFields(['encounterGroups']); + } + + if (changedValues.opportunityLevel !== undefined && initialOpportunity) { + if (initialOpportunity.opportunityLevel !== changedValues.opportunityLevel) { + mergedOpportunity.opportunityLevels = []; + } else { + mergedOpportunity.opportunityLevels = initialOpportunity.opportunityLevels; + } + form.resetFields(['opportunityLevels']); + } + + setOpportunity(mergedOpportunity); + form.setFieldsValue(mergedOpportunity); + }; + + const handleCancel = async () => { + const editModel = await opportunityService.getEditModel(opportunityGuid); + setOpportunity(editModel); + form.setFieldsValue(editModel); + if (onCancel) { + onCancel(); + } + }; + + const handleChargeCodesUploaded = (chargeCodes: string[]) => { + const updated = { ...opportunity, ...{ chargeCodes: chargeCodes } } as IChargeCodeOpportunitySaveModel; + setOpportunity(updated); + form.setFieldsValue(updated); + setUploadModalVisible(false); + }; + + const handleOpportunitySave = async (updatedOpportunity: IChargeCodeOpportunitySaveModel) => { + if (onUpdate && updatedOpportunity.name.trim() !== '' && RegEx.JazzRestrictedCharacters.test(updatedOpportunity.name)) { + onUpdate(updatedOpportunity); + } + }; + + const getOpportunityLevelFieldInfo = (): IScoreFieldInfo => { + if (modalEntityProperties && modalServiceLineProperties) { + switch (opportunity?.opportunityLevel) { + case OpportunityLevelTypes.Entity: + return { + dimensionName: modalEntityProperties.entityLabel, + dimensionGuidOrGlobalId: modalEntityProperties.departmentRollupGlobalId, + modalTitle: modalEntityProperties.entityTitle + }; + case OpportunityLevelTypes.ServiceLine: + return { + dimensionName: modalServiceLineProperties.serviceLineName, + dimensionGuidOrGlobalId: modalServiceLineProperties.serviceLineDimensionGuid, + modalTitle: modalServiceLineProperties.serviceLineTitle + }; + case OpportunityLevelTypes.Specialty: + return { + dimensionName: 'DSS Physician Specialty', + dimensionGuidOrGlobalId: 'DSS Physician Specialty', + modalTitle: 'Physician Specialties' + }; + case OpportunityLevelTypes.Provider: + return { + dimensionName: 'Primary Physicians', + dimensionGuidOrGlobalId: 'Physician Primary', + modalTitle: 'Primary Physicians' + }; + default: + return { + dimensionName: '', + dimensionGuidOrGlobalId: '', + modalTitle: '' + }; + } + } else { + return { + dimensionName: '', + dimensionGuidOrGlobalId: '', + modalTitle: '' + }; + } + }; + + const opportunityLevelFieldInfo = getOpportunityLevelFieldInfo(); + + const getEncounterGroupFieldInfo = (): IScoreFieldInfo => { + if (opportunity?.encounterGroup === EncounterGroup.PatientPopulation) { + return { + dimensionName: 'Patient Populations', + dimensionGuidOrGlobalId: 'Clinical Indicator', + modalTitle: 'Patient Populations' + }; + } else { + return { + dimensionName: 'Case Type', + dimensionGuidOrGlobalId: 'Case Type', + hierarchyGuid: 'c20b06b1-5a60-4fa5-825d-8e9a0524f783', + filters: [{ op: 'Equals', attributeGuid: explorationPopulationIdAttributeGuid, value: 0 }], + modalTitle: 'Case Types' + }; + } + }; + const encounterGroupFieldInfo = getEncounterGroupFieldInfo(); + + const chargeCodeLabel = ( + <> + + + Charge Codes + + + + + ); + + return ( + + + + + + + + + + } + > +
+ ({ + async validator(_, value) { + if (!value || (await nameValidationService.nameIsValid(userSetting.configurationGuid, OpportunityTypes.ChargeCode, value, opportunityGuid))) { + return Promise.resolve(); + } + return Promise.reject(nameIsDuplicate); + } + }) + ]} + > + + + + + + + + + + + + + + + {modalEntityProperties && modalServiceLineProperties && opportunity?.opportunityLevel !== undefined && opportunity.opportunityLevel !== OpportunityLevelTypes.Organization && ( + + + + )} + + + +
+ setUploadModalVisible(false)} + onOk={handleChargeCodesUploaded} + /> +
+ ); +}; + +export default ChargeCodeOpportunityEditModal; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/ChargeCodeUploadModal.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/ChargeCodeUploadModal.tsx new file mode 100644 index 0000000..992fc00 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/ChargeCodeUploadModal.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from 'react'; +import { opportunityService } from './data/opportunityService'; +import Button from '@strata/tempo/lib/button'; +import Divider from '@strata/tempo/lib/divider'; +import Modal from '@strata/tempo/lib/modal'; +import Spacing from '@strata/tempo/lib/spacing'; +import Tag from '@strata/tempo/lib/tag'; +import Text from '@strata/tempo/lib/text'; +import UploadButton, { UploadChangeParam } from '@strata/tempo/lib/uploadbutton'; +import DownloadIcon from '@strata/tempo/lib/icon/DownloadIcon'; + +export interface IChargeCodeUploadModalProps { + isVisible: boolean; + opportunityGuid: string; + chargeCodes: string[]; + onOk: (chargeCodes: string[]) => void; + onCancel: () => void; +} + +const ChargeCodeUploadModal: React.FC = (props: IChargeCodeUploadModalProps) => { + const { isVisible, opportunityGuid, chargeCodes, onOk, onCancel } = props; + const [visible, setVisible] = useState(false); + + useEffect(() => { + if (isVisible) { + setVisible(isVisible); + } + }, [isVisible]); + + const handleModalClose = () => { + setVisible(false); + onCancel(); + }; + + const downloadFile = async () => { + await opportunityService.exportChargeCodes(opportunityGuid, chargeCodes); + }; + + const uploadFile = async (info: UploadChangeParam) => { + const chargeCodes = await opportunityService.importChargeCodes(info.file as unknown as File); + const hpaths = chargeCodes.map((result: { costDriver: string; rollup: string; chargeCodeId: string }) => `CC|CCRU|${result.costDriver}|${result.rollup}|${result.chargeCodeId}`); + setVisible(false); + onOk(hpaths); + }; + + return ( + handleModalClose()}> + + + + STEP 1 + + Download the Excel template + + + + + + + + + + STEP 2 + + Upload file with charge codes to add + + + + + + + ); +}; + +export default ChargeCodeUploadModal; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/Filters.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/Filters.tsx new file mode 100644 index 0000000..a1f7144 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/Filters.tsx @@ -0,0 +1,122 @@ +import React, { useCallback } from 'react'; +import Button from '@strata/tempo/lib/button'; +import Form from '@strata/tempo/lib/form'; +import Drawer from '@strata/tempo/lib/drawer'; +import DropDown from '@strata/tempo/lib/dropdown'; +import RadioGroup from '@strata/tempo/lib/radiogroup'; +import InputNumber from '@strata/tempo/lib/inputnumber'; +import Spacing from '@strata/tempo/lib/spacing'; +import Checkbox from '@strata/tempo/lib/checkbox'; +import { initialFilters, IChargeCodeFilters, ChargeCodeGroup } from './data/IChargeCodeFilters'; +import { useEffect, useState } from 'react'; +import { opportunityService } from './data/opportunityService'; +import { IListMember } from '../../shared/data/IListMember'; +import { useConfigurations } from '../../shared/configuration/ConfigurationContext'; +import { convertNullOrEmptyStrToUndefined } from '../../shared/utilities'; +import { SeasonalityType } from '../../shared/data/SeasonalityType'; + +interface IFiltersProps { + filters: IChargeCodeFilters; + isVisible: boolean; + onApply: (filters: IChargeCodeFilters) => void; + onCancel: () => void; +} + +const Filters: React.FC = (props: IFiltersProps) => { + const [opportunityLevels, setOpportunityLevels] = useState(); + const { currentConfiguration } = useConfigurations(); + + const [form] = Form.useForm(); + const handleClearFilters = useCallback(() => { + form.setFieldsValue(initialFilters); + }, [form]); + + useEffect(() => { + const loadFiltersOptions = async () => { + if (currentConfiguration == null) { + return; + } + const { opportunityLevels } = await opportunityService.getFilterOptions(currentConfiguration.configurationGuid); + + setOpportunityLevels(opportunityLevels); + handleClearFilters(); + }; + + loadFiltersOptions(); + }, [currentConfiguration, handleClearFilters]); + + const handleApplyFilters = () => { + const filters: IChargeCodeFilters = convertNullOrEmptyStrToUndefined(form.getFieldsValue()); + props.onApply(filters); + }; + + const handleCancel = () => { + props.onCancel(); + form.setFieldsValue(props.filters); + }; + + return ( + + + + + + + + } + visible={props.isVisible} + onClose={handleCancel} + > +
+ + + + + + + + + + + + + + + + + + + + Exclude Opportunities with Initiatives + +
+
+ ); +}; + +export default Filters; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/Opportunities.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/Opportunities.tsx new file mode 100644 index 0000000..8e52907 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/Opportunities.tsx @@ -0,0 +1,114 @@ +import { useJazzUrlWithVersion } from '@strata/core/lib'; +import { useOidcSession } from '@strata/core/lib/auth'; +import Button from '@strata/tempo/lib/button/Button'; +import DataGrid from '@strata/tempo/lib/datagrid'; +import PlusIcon from '@strata/tempo/lib/icon/PlusIcon'; +import React, { useEffect, useState } from 'react'; +import { useHistory } from 'react-router'; +import { useConfigurations } from '../../shared/configuration/ConfigurationContext'; +import OpportunitiesGrid from '../../shared/opportunities/OpportunitiesGrid'; +import { IOpportunity } from '../../shared/opportunities/data'; +import Filters from './Filters'; +import { IChargeCodeFilters, initialFilters, numberOfFilters } from './data/IChargeCodeFilters'; +import { opportunityService } from './data/opportunityService'; + +const Opportunities: React.FC = () => { + const [filters, setFilters] = useState(initialFilters); + const [filtersVisible, setFiltersVisible] = useState(false); + const jazzUrl = useJazzUrlWithVersion(); + const { currentConfiguration } = useConfigurations(); + const [currentConfigurationGuid, setCurrentConfigurationGuid] = useState(currentConfiguration?.configurationGuid ?? ''); + const [chargeCodeBeta, setChargeCodeBeta] = useState(false); + const history = useHistory(); + const session = useOidcSession(); + + useEffect(() => { + const checkForBeta = async () => { + const isChargeCodeBeta = await opportunityService.chargeCodeBeta(); + setChargeCodeBeta(isChargeCodeBeta); + }; + document.title = 'Charge Code Opportunities'; + checkForBeta(); + }, []); + + useEffect(() => { + if (currentConfiguration == null) { + return; + } + setFilters(initialFilters); + setCurrentConfigurationGuid(currentConfiguration.configurationGuid); + }, [currentConfiguration]); + + const handleApplyFilters = (newFilters: IChargeCodeFilters) => { + setFiltersVisible(false); + setFilters({ ...filters, ...newFilters }); + }; + + const handleSearch = (searchValue: string) => { + setFilters((filters) => { + return { ...filters, opportunitySearch: searchValue }; + }); + }; + + const getOpportunityUrl = (opportunity: IOpportunity) => { + return `/charge-code-opportunity/${opportunity.opportunityGuid}`; + }; + + const getInitiativeUrl = (opportunity: IOpportunity) => { + return `${jazzUrl}CCI/ChargeCode/InitiativeViewer.aspx?id=${opportunity.initiativeGuid}`; + }; + + const addOpportunityButton = ( + <> + {chargeCodeBeta || session.isStrata ? ( + + ) : ( + '' + )} + + ); + + return ( + <> + setFiltersVisible(false)} /> + setFiltersVisible(true)} + onSearch={handleSearch} + customAction={addOpportunityButton} + > + + + + (args.rowData[args.field] === 'ServiceLine' ? 'Service Line' : args.rowData[args.field])} + /> + (args.rowData[args.field] ? 'With' : 'Without')} /> + + + ); +}; + +export default Opportunities; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/ICCOpportunityRowData.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/ICCOpportunityRowData.ts new file mode 100644 index 0000000..509346c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/ICCOpportunityRowData.ts @@ -0,0 +1,12 @@ +export interface ICCOpportunityRowData { + name: string; + totalCases: number; + baselineUnitsOfServicePerCase: number; + baselineTotalUnitsOfService: number; + baselineCostPerCase: number; + baselineTotalCost: number; + goalUnitsOfServicePerCase: number; + goalTotalUnitsOfService: number; + goalCostPerCase: number; + goalTotalCost: number; +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/ICCOpportunityTreeGridModel.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/ICCOpportunityTreeGridModel.ts new file mode 100644 index 0000000..c238d91 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/ICCOpportunityTreeGridModel.ts @@ -0,0 +1,7 @@ +import { ICCOpportunityRowData } from './ICCOpportunityRowData'; + +export interface ICCOpportunityTreeGridModel { + key: string; + data: ICCOpportunityRowData; + children: ICCOpportunityTreeGridModel[]; +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCode.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCode.ts new file mode 100644 index 0000000..db12590 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCode.ts @@ -0,0 +1,8 @@ +export interface IChargeCode { + chargeCodeId: number; + chargeCode: string; + memberGuid: string; + name: string; + costDriver: string; + rollup: string; +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeFilterOptions.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeFilterOptions.ts new file mode 100644 index 0000000..3947c37 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeFilterOptions.ts @@ -0,0 +1,14 @@ +import { IListMember } from '../../../shared/data/IListMember'; + +export interface IChargeCodeFilterOptions { + opportunityLevels: IListMember[]; +} + +export const newFilterOptions = (data: Partial = {}): IChargeCodeFilterOptions => { + return { + ...{ + opportunityLevels: [] + }, + ...data + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeFilters.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeFilters.ts new file mode 100644 index 0000000..55d7b5c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeFilters.ts @@ -0,0 +1,38 @@ +import { SeasonalityType } from '../../../shared/data/SeasonalityType'; +import { IOpportunityFilters } from '../../../shared/opportunities/data'; + +export interface IChargeCodeFilters extends IOpportunityFilters { + cases?: number; + opportunityLevels: string[]; + chargeCodeGroups: ChargeCodeGroup; + seasonality: SeasonalityType; +} + +export enum ChargeCodeGroup { + Both = -1, + CaseType = 0, + PatientPopulation = 1 +} + +export const initialFilters: IChargeCodeFilters = { + identifiedSavings: undefined, + cases: undefined, + chargeCodeGroups: ChargeCodeGroup.Both, + opportunityLevels: [], + seasonality: SeasonalityType.WithAndWithout, + viewVisible: true, + excludeInitiatives: false, + opportunitySearch: '' +}; + +export const numberOfFilters = (filters: IChargeCodeFilters): number => { + let filtersCount = 0; + if (filters.identifiedSavings != null) filtersCount++; + if (filters.cases != null) filtersCount++; + if (filters.chargeCodeGroups !== ChargeCodeGroup.Both) filtersCount++; + if (filters.opportunityLevels.length > 0) filtersCount++; + if (filters.seasonality !== SeasonalityType.WithAndWithout) filtersCount++; + if (filters.viewVisible === false) filtersCount++; + if (filters.excludeInitiatives === true) filtersCount++; + return filtersCount; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeOpportunity.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeOpportunity.ts new file mode 100644 index 0000000..e0f7aa5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeOpportunity.ts @@ -0,0 +1,22 @@ +import { IOpportunityDetails } from '../../../shared/opportunities/data/IOpportunity'; +import { ICCOpportunityTreeGridModel } from './ICCOpportunityTreeGridModel'; + +export interface IChargeCodeOpportunity extends IOpportunityDetails { + opportunityId: number; + opportunityKey: string; + name: string; + totalCases: number; + costPerCase: number; + goalCostPerCase: number; + improvementCostPerCase: number; + totalCost: number; + goalTotalCost: number; + improvementTotalCost: number; + opportunityLevel: string; + chargeCodeJson: string; + clinicalIndicatorJson: string; + opportunityLevelJson: string; + treeGridData: ICCOpportunityTreeGridModel[]; + chargeCodeGroupValue: string; + opportunityLevels: string[]; +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeOpportunityDetailChanges.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeOpportunityDetailChanges.ts new file mode 100644 index 0000000..cefbcb1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeOpportunityDetailChanges.ts @@ -0,0 +1,11 @@ +export interface IChargeCodeOpportunityDetailChanges { + key: string; + goalTotalUnitsOfService?: number; + goalCostPerUnit?: number; +} + +export const DefaultChargeCodeOpportunityDetailChanges: IChargeCodeOpportunityDetailChanges = { + key: '', + goalTotalUnitsOfService: undefined, + goalCostPerUnit: undefined +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeOpportunitySaveModel.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeOpportunitySaveModel.ts new file mode 100644 index 0000000..b27abb7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/IChargeCodeOpportunitySaveModel.ts @@ -0,0 +1,33 @@ +import { EncounterGroup } from '../../../shared/opportunities/data/EncounterGroup'; +import { OpportunityLevelTypes } from '../../../shared/opportunities/data/OpportunityLevelTypes'; + +export interface IChargeCodeOpportunitySaveModel { + configurationGuid: string; + opportunityGuid: string; + name: string; + encounterGroup: EncounterGroup; + encounterGroups: string[]; + chargeCodes: string[]; + opportunityLevel: OpportunityLevelTypes | undefined; + opportunityLevels: string[]; + isSeasonal: boolean; + isFromWizard: boolean; +} + +export const newChargeCodeOpportunitySaveModel = (configurationGuid: string, data: Partial = {}): IChargeCodeOpportunitySaveModel => { + return { + ...{ + configurationGuid: configurationGuid, + opportunityGuid: '00000000-0000-0000-0000-000000000000', + name: '', + encounterGroup: EncounterGroup.CaseType, + encounterGroups: [], + chargeCodes: [], + opportunityLevel: undefined, + opportunityLevels: [], + isSeasonal: false, + isFromWizard: false + }, + ...data + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/opportunityService.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/opportunityService.ts new file mode 100644 index 0000000..ad41ea0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/charge-code/opportunities/data/opportunityService.ts @@ -0,0 +1,120 @@ +import { appConfig, getSecureService } from '@strata/core/lib'; +import { IInitiative } from '../../../shared/initiatives/data/IInitiative'; +import { IOpportunityFilters, IOpportunityService } from '../../../shared/opportunities/data'; +import { IPagedApiResponse, IPagingOptions, ISortOptions } from '../../../shared/paging'; +import { IChargeCode } from './IChargeCode'; +import { IChargeCodeFilterOptions } from './IChargeCodeFilterOptions'; +import { IChargeCodeOpportunity } from './IChargeCodeOpportunity'; +import { IChargeCodeOpportunitySaveModel } from './IChargeCodeOpportunitySaveModel'; +import { IChargeCodeOpportunityDetailChanges } from './IChargeCodeOpportunityDetailChanges'; +import { IChargeCodeCustomPickerModel } from '../../../strategic-opportunities/data/IChargeCodeCustomPickerModel'; + +const { httpGet, httpPost, httpPut, httpDelete, httpDownloadAndDisplayFile } = getSecureService(`${appConfig.apiUrl}v1/`); +const { httpUpload } = getSecureService(`${appConfig.scoreUrl}`); +const url = 'charge-code-opportunities'; + +export const opportunityService: IOpportunityService = { + getOpportunities: async (configurationGuid: string, pagingOptions: IPagingOptions, filters: IOpportunityFilters): Promise> => { + return httpPost>(`charge-code-opps`, { + pagingOptions, + filters: { + ...filters, + configurationGuid + } + }); + }, + + export: async (configurationGuid: string, sortOptions: ISortOptions, filters: IOpportunityFilters): Promise => { + const date = new Date(); + const fileName = 'ChargeCode_CI_Opportunities_' + date.toISOString().split('T')[0]; + httpDownloadAndDisplayFile( + 'charge-code-opps/export', + { + method: 'post', + body: JSON.stringify({ + sortOptions, + filters: { + ...filters, + configurationGuid + } + }) + }, + fileName + ); + }, + + getFilterOptions: (configurationGuid: string): Promise => { + return httpGet(`charge-code-opps/${configurationGuid}/filter-options`); + }, + + setHidden: async (opportunityGuid: string, isHidden: boolean): Promise => { + await httpPut(`charge-code-opps/${opportunityGuid}/set-hidden`, isHidden); + }, + + setNote: async (opportunityGuid: string, note: string): Promise => { + await httpPut(`charge-code-opps/${opportunityGuid}/set-note`, note); + }, + updateChargeCodeOpportunity: async (opportunity: IChargeCodeOpportunitySaveModel): Promise => { + return httpPut(`${url}/${opportunity.opportunityGuid}/update-opportunity`, opportunity); + }, + validateChargeCodes: async (chargeCodes: string[]): Promise => { + return httpPost(`${url}/validate-charge-codes`, { chargeCodes }); + }, + getChargeCodePickerModels: async (hPaths: string[]): Promise => { + return httpPost(`${url}/charge-code-members`, hPaths); + }, + validateOpportunityLevels: async (opportunityLevel: number, opportunityLevels: string[], configuration: string): Promise => { + return httpPost(`${url}/validate-opportunity-levels`, { opportunityLevel, opportunityLevels, configuration }); + }, + getById(opportunityGuid: string | number): Promise { + return httpGet(`${url}/${opportunityGuid}`); + }, + getEditModel(opportunityGuid: string | number): Promise { + return httpGet(`${url}/${opportunityGuid}/edit-model`); + }, + updateGoalTotalUnitsOfService(opportunityGuid: string, key: string, goalValue: number): Promise { + return httpPut(`${url}/${opportunityGuid}/update-goal-total-units-of-service`, { key, goalValue }); + }, + updateGoalCostPerUnit(opportunityGuid: string, key: string, goalValue: number): Promise { + return httpPut(`${url}/${opportunityGuid}/update-goal-cost-per-unit`, { key, goalValue }); + }, + updateGoals(opportunityGuid: string, changes: IChargeCodeOpportunityDetailChanges[]): Promise { + return httpPut(`${url}/${opportunityGuid}/update-goals`, changes); + }, + delete: async (opportunityGuid: string, callback: () => void, failed: (err: any) => void): Promise => { + httpDelete(`${url}/${opportunityGuid}/delete`) + .then(callback) + .catch((e) => failed(e)); + }, + getInitiativeInfo: (opporturnityGuid: string): Promise => { + return httpGet(`${url}/${opporturnityGuid}/initiative-info`); + }, + exportChargeCodes: function (opportunityGuid: string | undefined, chargeCodes: string[] | undefined) { + const filename = 'ChargeCodes.xlsx'; + const body = JSON.stringify({ chargeCodes }); + httpDownloadAndDisplayFile( + `${url}/${opportunityGuid ?? '00000000-0000-0000-0000-000000000000'}/export`, + { + method: 'post', + body: body + }, + filename + ); + }, + + importChargeCodes: async (file: File): Promise => { + const globalId = 'Charge Code'; + const cols = ['chargeCodeId', 'costDriver', 'rollup']; + const columns = cols.map((column) => `columns=${column}`).join('&'); + const formData = new FormData(); + const requestPath = `dimensions/${globalId}/members-filtered-by-file?${columns}&valueFilterColumn=ChargeCode&maxResults=1000`; + formData.append('valuesToFilterFile', file); + + const results = await httpUpload(requestPath, formData, undefined, 'post'); + return results as IChargeCode[]; + }, + + chargeCodeBeta: async (): Promise => { + return httpGet(`${url}/opportunity-create-enabled`); + } +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/configuration/tabs/AddPeerGroupDrawer.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/configuration/tabs/AddPeerGroupDrawer.tsx new file mode 100644 index 0000000..968015b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/configuration/tabs/AddPeerGroupDrawer.tsx @@ -0,0 +1,175 @@ +import Button from '@strata/tempo/lib/button/Button'; +import Drawer from '@strata/tempo/lib/drawer/Drawer'; +import DropDown, { DropDownValue } from '@strata/tempo/lib/dropdown/DropDown'; +import Form from '@strata/tempo/lib/form/Form'; +import Input from '@strata/tempo/lib/input/Input'; +import Spacing from '@strata/tempo/lib/spacing/Spacing'; +import React, { useEffect, useState } from 'react'; +import { IPeerGroupFilters } from '../../shared/configuration/IPeerGroupFilters'; +import { RegEx } from '../../shared/validation/RegEx'; +import { PeerGroupDropdownItems } from './data/PeerGroupDropdownItems'; +import { peerGroupService } from './data/peerGroupService'; +import InfoCircleIcon from '@strata/tempo/lib/icon/InfoCircleIcon'; +import { useHelpCenter } from '@strata/core/lib/help/useHelpCenter'; + +export interface IPeerGroupDefinitionDrawerProps { + isVisible: boolean; + title: string; + editPeerGroup: IPeerGroupFilters | undefined; + onApply?: (filters: IPeerGroupFilters) => void; + onClose: () => void; +} + +const nameIsDuplicate = 'Peer Group name already exists.'; + +const AddPeerGroupDrawer: React.FC = (props) => { + const { isVisible, title, editPeerGroup, onApply, onClose } = props; + const [isLoading, setIsLoading] = useState(); + const [drawerFilters, setDrawerFilters] = useState(); + const [dropdownItems, setDropdownItems] = useState(); + + const [form] = Form.useForm(); + const helpCenter = useHelpCenter(); + + useEffect(() => { + const fetchFilters = async () => { + setIsLoading(true); + try { + const items = await peerGroupService.getDropdownItems(); + setDropdownItems(items); + if (!editPeerGroup && !drawerFilters) { + const filters = { + peerGroupId: 0, + name: '', + entityTypes: items.entityTypeList.map((i) => i.value), + censusRegions: items.censusRegionList.map((i) => i.value), + operatingExpenses: items.operatingExpenseList.map((i) => i.value), + amcTypes: items.amcTypeList.map((i) => i.value), + bedSize: items.bedSizeList.map((i) => i.value), + urbanRural: items.urbanRuralList.map((i) => i.value) + }; + setDrawerFilters(filters); + form.setFieldsValue(filters); + } else if (editPeerGroup && !drawerFilters) { + setDrawerFilters(editPeerGroup); + form.setFieldsValue(editPeerGroup); + } + } finally { + setIsLoading(false); + } + }; + if (isVisible && !dropdownItems) { + fetchFilters(); + } + }, [isVisible, dropdownItems, drawerFilters, editPeerGroup, form]); + + const handleClear = () => { + if (dropdownItems) form.setFieldsValue(getInitialFilters(dropdownItems)); + }; + + const getInitialFilters = (dropdownItems: PeerGroupDropdownItems): IPeerGroupFilters => { + const filters: IPeerGroupFilters = { + peerGroupId: 0, + name: '', + entityTypes: [], + censusRegions: [], + operatingExpenses: [], + amcTypes: [], + bedSize: [], + urbanRural: [] + }; + return filters; + }; + + const handleCancel = () => { + onClose(); + }; + + const handleFormFinish = async () => { + if (onApply) { + const formValues = form.getFieldsValue(); + onApply(formValues); + } + onClose(); + }; + + const handleDropDownChange = (value: DropDownValue, fieldName: 'entityTypes' | 'censusRegions' | 'operatingExpenses' | 'amcTypes' | 'bedSize' | 'urbanRural') => { + // Tempo may call onChange with undefined/empty during open/close; ignore no-op transitions + if (value == null) return; + const arr = Array.isArray(value) ? (value as number[]) : [value as number]; + // If it’s an “open/close” transient empty, keep existing selection + if (arr.length === 0) { + const current = form.getFieldValue(fieldName) as number[] | undefined; + if (current && current.length > 0) return; + } + form.setFieldValue(fieldName, arr); + }; + return ( + onClose()} + loading={isLoading} + width={600} + footer={ + + + + + + + + + + } + > +
+ ({ + async validator(_, value) { + if (!value) { + return Promise.resolve(); + } + if (!(await peerGroupService.nameIsValid(editPeerGroup?.peerGroupId ?? 0, value))) { + return Promise.reject(nameIsDuplicate); + } + return Promise.resolve(); + } + }) + ]} + > + + + + handleDropDownChange(value, 'entityTypes')} /> + + + handleDropDownChange(value, 'urbanRural')} /> + + + handleDropDownChange(value, 'amcTypes')} /> + + + handleDropDownChange(value, 'censusRegions')} /> + + + handleDropDownChange(value, 'bedSize')} /> + + + handleDropDownChange(value, 'operatingExpenses')} /> + +
+
+ ); +}; + +export default AddPeerGroupDrawer; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/configuration/tabs/PeerGroupTab.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/configuration/tabs/PeerGroupTab.tsx new file mode 100644 index 0000000..a1f8a48 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/configuration/tabs/PeerGroupTab.tsx @@ -0,0 +1,144 @@ +import ActionBar from '@strata/tempo/lib/actionbar/ActionBar'; +import Button from '@strata/tempo/lib/button/Button'; +import DataGrid from '@strata/tempo/lib/datagrid/DataGrid'; +import DeleteIcon from '@strata/tempo/lib/icon/DeleteIcon'; +import EditIcon from '@strata/tempo/lib/icon/EditIcon'; +import PlusIcon from '@strata/tempo/lib/icon/PlusIcon'; +import useModal from '@strata/tempo/lib/modal/useModal'; +import Tooltip from '@strata/tempo/lib/tooltip/Tooltip'; +import React, { useEffect, useState } from 'react'; +import { IPeerGroupFilters } from '../../shared/configuration/IPeerGroupFilters'; +import { MAX_PEER_GROUP } from '../../shared/constants'; +import AddPeerGroupDrawer from './AddPeerGroupDrawer'; +import { peerGroupService } from './data/peerGroupService'; + +export interface IPeerGroupTabProps { + setLoading: (loading: boolean) => void; +} + +const addDrawerTitle = 'Add Peer Group'; + +const PeerGroupTab: React.FC = (props: IPeerGroupTabProps) => { + const { setLoading } = props; + const [peerGroup, setPeerGroup] = useState(); + const [peerGroups, setPeerGroups] = useState([]); + const [isPeerGroupDrawerVisible, setIsPeerGroupDrawerVisible] = useState(false); + + const modal = useModal(); + + useEffect(() => { + loadData(); + }, []); + + const loadData = async () => { + setLoading(true); + try { + const loadedPeerGroups = await peerGroupService.getGroups(); + setPeerGroups(loadedPeerGroups); + } finally { + setLoading(false); + } + }; + + const handleAddPeerGroup = () => { + setPeerGroup(undefined); + setIsPeerGroupDrawerVisible(true); + }; + + const handleEditPeerGroup = async (peerGroupId: number) => { + const editPeerGroup = await peerGroupService.getById(peerGroupId); + setPeerGroup(editPeerGroup); + setIsPeerGroupDrawerVisible(true); + }; + + const handleDeletePeerGroup = async (peerGroup: any) => { + modal.confirm({ + title: `Delete ${peerGroup.name}?`, + content: 'Are you sure you want to delete this peer group?', + okText: 'Delete Peer Group', + cancelText: 'Cancel', + confirmType: 'danger', + async onOk() { + setLoading(true); + try { + await peerGroupService.delete(peerGroup.peerGroupId); + await loadData(); + } finally { + setLoading(false); + } + } + }); + }; + + const onClosePeerGroupDefinition = () => { + setIsPeerGroupDrawerVisible(false); + }; + + const onPeerGroupFiltersChange = async (filterSettings: IPeerGroupFilters) => { + setLoading(true); + try { + if (peerGroup) { + await peerGroupService.update(peerGroup.peerGroupId, filterSettings); + } else { + await peerGroupService.create(filterSettings); + } + await loadData(); + } finally { + setLoading(false); + } + }; + + return ( + <> + = MAX_PEER_GROUP ? `You have reached the maximum number of ${MAX_PEER_GROUP} peer groups. Please delete a peer group or modify an existing peer group.` : ''} + > + + + } + /> + + + + + + ( + + + + } + > + + + + + + ( + <> + + {' '} + is created. + + ) + }); + }; + + const getMetrics = () => { + return ( + + ); + }; + + return ( + <> + history.push(`/initiatives/edit/encounter`, { opportunityGuid: opportunityGuid })} + getInitiativeInfo={getInitiativeInfo} + finalizeInitiative={finalizeInitiative} + /> + {getMetrics()} + + {dataGrids} + + + + { + setEncounterEditModalVisible(true); + }} + /> + + + + {initiativeModalVisible && ( + + )} + {encounterEditModalVisible && ( + { + setEncounterEditModalVisible(false); + }} + onUpdate={(updatedOpportunity: IEncounterOpportunity) => { + onOpporunityEdit(updatedOpportunity); + }} + /> + )} + + ); +}; + +export default EncounterOpportunityDetail; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/EncounterOpportunityDetailGrid.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/EncounterOpportunityDetailGrid.tsx new file mode 100644 index 0000000..017f14c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/EncounterOpportunityDetailGrid.tsx @@ -0,0 +1,140 @@ +import React from 'react'; +import Spacing from '@strata/tempo/lib/spacing'; +import DataGrid from '@strata/tempo/lib/datagrid/DataGrid'; +import DataHeaderCell from '@strata/tempo/lib/dataheader/DataHeaderCell'; +import DataHeaderRow from '@strata/tempo/lib/dataheader/DataHeaderRow'; +import DataHeaderGroup from '@strata/tempo/lib/dataheader/DataHeaderGroup'; +import { IEncounterGridRow } from './data/IEncounterOpportunity'; +import ICellEditorArgs from '@strata/tempo/lib/datacolumn/ICellEditorArgs'; +import ICellArgs from '@strata/tempo/lib/datacolumn/ICellArgs'; + +export interface EncounterOpportunityDetailGridProps { + gridName: string; + gridData: IEncounterGridRow[]; + gridLoading: boolean; + isEditable: boolean; + isLocked: boolean; + handleCellEdit: (gridRef: React.RefObject, args: ICellEditorArgs, newCellValue: number) => void; +} + +const EncounterOpportunityDetailGrid: React.FC = (props: EncounterOpportunityDetailGridProps) => { + const gridRef = React.useRef(null); + + const { gridData, gridLoading, gridName, isEditable, isLocked, handleCellEdit } = props; + + const onCellEdit = (args: ICellEditorArgs, newCellValue: number) => { + handleCellEdit(gridRef, args, newCellValue); + }; + + const isCellEditable = (args: ICellArgs) => isEditable && !isLocked && args.row.text === 'Goal'; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + > + + + + + + + + + + + + + + + ); +}; + +export default EncounterOpportunityDetailGrid; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/EncounterOpportunityEditModal.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/EncounterOpportunityEditModal.tsx new file mode 100644 index 0000000..265287e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/EncounterOpportunityEditModal.tsx @@ -0,0 +1,262 @@ +import React, { useEffect, useState } from 'react'; +import Modal from '@strata/tempo/lib/modal'; +import Form from '@strata/tempo/lib/form'; +import Input from '@strata/tempo/lib/input'; +import Button from '@strata/tempo/lib/button'; +import Spacing from '@strata/tempo/lib/spacing'; +import { useHelpCenter } from '@strata/core/lib'; +import { configurationService } from '../../shared/configuration/configurationService'; +import UserSettingContext from '../../shared/userSetting/UserSettingContext'; +import { ScoreMemberPicker } from '@strata/score/lib/ScoreMemberPicker'; +import { RegEx } from '../../shared/validation/RegEx'; +import { OpportunityLevelTypes } from '../../shared/opportunities/data/OpportunityLevelTypes'; +import DropDown from '@strata/tempo/lib/dropdown'; +import { IEntityInfo } from '../../shared/configuration/IEntityInfo'; +import { IServiceLineInfo } from '../../shared/configuration/IServiceLineInfo'; +import { IScoreFieldInfo } from '../../shared/opportunities/data/IScoreFieldInfo'; +import RadioGroup from '@strata/tempo/lib/radiogroup'; +import { EncounterGroup } from '../../shared/opportunities/data/EncounterGroup'; +import InfoCircleIcon from '@strata/tempo/lib/icon/InfoCircleIcon'; +import { IEncounterOpportunity } from './data/IEncounterOpportunity'; +import { nameIsDuplicate, nameValidationService } from '../../shared/opportunities/data/nameValidationService'; +import { OpportunityTypes } from '../../opportunities/opportunities/data/IAllOpportunity'; +import { explorationPopulationIdAttributeGuid } from '../../shared/utilities'; + +export interface IEncounterOpportunityEditModalProps { + isVisible: boolean; + initialOpportunity: IEncounterOpportunity | undefined; + onCancel?: () => void; + onUpdate?: (updatedOpportunity: IEncounterOpportunity) => void; +} + +const EncounterOpportunityEditModal: React.FC = (props: IEncounterOpportunityEditModalProps) => { + const { isVisible, initialOpportunity, onCancel, onUpdate } = props; + const [form] = Form.useForm(); + const [opportunity, setOpportunity] = useState(initialOpportunity); + const [loading, setLoading] = useState(false); + const [modalEntityProperties, setModalEntityProperties] = useState(); + const [modalServiceLineProperties, setModalServiceLineProperties] = useState(); + const helpCenter = useHelpCenter(); + + const { userSetting } = React.useContext(UserSettingContext); + + useEffect(() => { + const loadData = async (configurationGuid: string) => { + const entityInfo = await configurationService.getEntityInfo(configurationGuid); + const serviceLineInfo = await configurationService.getServiceLineInfo(configurationGuid); + setModalEntityProperties(entityInfo); + setModalServiceLineProperties(serviceLineInfo); + }; + if (userSetting.configurationGuid !== '') { + setLoading(true); + loadData(userSetting.configurationGuid).finally(() => { + setLoading(false); + }); + } + }, [userSetting.configurationGuid]); + + // Save form, clear fields + const handleFormFinish = (values: any) => { + handleOpportunitySave({ ...opportunity, ...values }); + }; + + const handleValuesChanged = (changedValues: any) => { + const mergedOpportunity = { ...opportunity, ...changedValues } as IEncounterOpportunity; + + if (changedValues.encounterGroup !== undefined && initialOpportunity) { + if (initialOpportunity.encounterGroup !== changedValues.encounterGroup) { + mergedOpportunity.encounterGroupIds = []; + } else { + mergedOpportunity.encounterGroupIds = initialOpportunity.encounterGroupIds; + } + form.resetFields(['encounterGroupIds']); + } + + if (changedValues.opportunityLevel !== undefined && initialOpportunity) { + if (initialOpportunity.opportunityLevel !== changedValues.opportunityLevel) { + mergedOpportunity.opportunityLevelIds = []; + } else { + mergedOpportunity.opportunityLevelIds = initialOpportunity.opportunityLevelIds; + } + form.resetFields(['opportunityLevelIds']); + } + + setOpportunity(mergedOpportunity); + form.setFieldsValue(mergedOpportunity); + }; + + const handleCancel = async () => { + if (initialOpportunity) { + setOpportunity(initialOpportunity); + form.setFieldsValue(initialOpportunity); + } + if (onCancel) { + onCancel(); + } + }; + + const handleOpportunitySave = async (updatedOpportunity: IEncounterOpportunity) => { + if (onUpdate && updatedOpportunity.name.trim() !== '' && RegEx.JazzRestrictedCharacters.test(updatedOpportunity.name)) { + onUpdate(updatedOpportunity); + } + }; + + const getOpportunityLevelFieldInfo = (): IScoreFieldInfo => { + if (modalEntityProperties && modalServiceLineProperties) { + switch (opportunity?.opportunityLevel) { + case OpportunityLevelTypes.Entity: + return { + dimensionName: modalEntityProperties.entityLabel, + dimensionGuidOrGlobalId: modalEntityProperties.departmentRollupGlobalId, + modalTitle: modalEntityProperties.entityTitle + }; + case OpportunityLevelTypes.ServiceLine: + return { + dimensionName: modalServiceLineProperties.serviceLineName, + dimensionGuidOrGlobalId: modalServiceLineProperties.serviceLineDimensionGuid, + modalTitle: modalServiceLineProperties.serviceLineTitle + }; + case OpportunityLevelTypes.Specialty: + return { + dimensionName: 'DSS Physician Specialty', + dimensionGuidOrGlobalId: 'Physician Specialty', + label: 'Physician Specialty', + modalTitle: 'Physician Specialties' + }; + case OpportunityLevelTypes.Provider: + return { + dimensionName: 'Primary Physicians', + dimensionGuidOrGlobalId: 'Physician Primary', + modalTitle: 'Primary Physicians' + }; + default: + return { + dimensionName: '', + dimensionGuidOrGlobalId: '', + modalTitle: '' + }; + } + } else { + return { + dimensionName: '', + dimensionGuidOrGlobalId: '', + modalTitle: '' + }; + } + }; + + const opportunityLevelFieldInfo = getOpportunityLevelFieldInfo(); + + const getEncounterGroupFieldInfo = (): IScoreFieldInfo => { + if (opportunity?.encounterGroup === EncounterGroup.PatientPopulation) { + return { + dimensionName: 'Patient Populations', + dimensionGuidOrGlobalId: 'Clinical Indicator', + modalTitle: 'Patient Populations' + }; + } else { + return { + dimensionName: 'Case Type', + dimensionGuidOrGlobalId: 'Case Type', + hierarchyGuid: 'c20b06b1-5a60-4fa5-825d-8e9a0524f783', + filters: [{ op: 'Equals', attributeGuid: explorationPopulationIdAttributeGuid, value: 0 }], + modalTitle: 'Case Types' + }; + } + }; + const encounterGroupFieldInfo = getEncounterGroupFieldInfo(); + + return ( + + + + + + + + + + } + > +
+ ({ + async validator(_, value) { + if (!value || (await nameValidationService.nameIsValid(userSetting.configurationGuid, OpportunityTypes.Encounter, value, opportunity?.opportunityGuid))) { + return Promise.resolve(); + } + return Promise.reject(nameIsDuplicate); + } + }) + ]} + > + + + + + + + + + + + + {modalEntityProperties && modalServiceLineProperties && opportunity?.opportunityLevel !== undefined && opportunity.opportunityLevel !== OpportunityLevelTypes.Organization && ( + + + + )} + + + +
+
+ ); +}; + +export default EncounterOpportunityEditModal; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/EncounterOpportunityModal.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/EncounterOpportunityModal.tsx new file mode 100644 index 0000000..c4e2ce1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/EncounterOpportunityModal.tsx @@ -0,0 +1,347 @@ +import React, { useEffect, useState } from 'react'; +import { hangfireService } from '../../shared/hangfire/hangfireService'; +import { IEncounterOpportunitySaveModel, newEncounterOpportunitySaveModel } from './data/IEncounterOpportunitySaveModel'; +import Modal from '@strata/tempo/lib/modal'; +import Form from '@strata/tempo/lib/form'; +import DropDown from '@strata/tempo/lib/dropdown/DropDown'; +import Input from '@strata/tempo/lib/input'; +import RadioGroup from '@strata/tempo/lib/radiogroup'; +import Button from '@strata/tempo/lib/button'; +import Spacing from '@strata/tempo/lib/spacing'; +import useToast from '@strata/tempo/lib/toast'; +import Link from '@strata/tempo/lib/link/Link'; +import { useHelpCenter, useJazzUrlWithVersion } from '@strata/core/lib'; +import { configurationService } from '../../shared/configuration/configurationService'; +import UserSettingContext from '../../shared/userSetting/UserSettingContext'; +import { ScoreMemberPicker } from '@strata/score/lib/ScoreMemberPicker'; +import { RegEx } from '../../shared/validation/RegEx'; +import { IEntityInfo, newEntityInfo } from '../../shared/configuration/IEntityInfo'; +import { IServiceLineInfo, newServiceLineInfo } from '../../shared/configuration/IServiceLineInfo'; +import { useInterval } from '../../shared/utilities'; +import { opportunityService } from './data/opportunityService'; +import { IOpportunityJobResults } from '../../shared/hangfire/IOpportunityJobResults'; +import { OpportunityLevelTypes } from '../../shared/opportunities/data/OpportunityLevelTypes'; +import { IScoreFieldInfo } from '../../shared/opportunities/data/IScoreFieldInfo'; +import { nameIsDuplicate, nameValidationService } from '../../shared/opportunities/data/nameValidationService'; +import { OpportunityTypes } from '../../opportunities/opportunities/data/IAllOpportunity'; +import { EncounterGroup } from '../../shared/opportunities/data/EncounterGroup'; +import InfoCircleIcon from '@strata/tempo/lib/icon/InfoCircleIcon'; + +// should the on(s) be EventTarget types +export interface IEncounterOpportunityModalProps { + isVisible: boolean; + onCancel?: () => void; + onAdd?: () => void; +} + +const EncounterOpportunityModal: React.FC = (props: IEncounterOpportunityModalProps) => { + const { isVisible, onCancel, onAdd } = props; + const [form] = Form.useForm(); + const [opportunity, setOpportunity] = useState(newEncounterOpportunitySaveModel('')); + + const [modalEntityProperties, setModalEntityProperties] = useState(newEntityInfo); + const [modalServiceLineProperties, setModalServiceLineProperties] = useState(newServiceLineInfo); + const helpCenter = useHelpCenter(); + const toast = useToast(); + + const { userSetting } = React.useContext(UserSettingContext); + + const [delay] = useState(1000); + const [isWaiting, setWaiting] = useState(false); + const [isSaving, setSaving] = useState(false); + const [name, setName] = useState(''); + const [jobResults, setJobResults] = useState({ jobId: '', opportunityGuid: '' }); + const jazzUrl = useJazzUrlWithVersion(); + + useEffect(() => { + const loadData = async (configurationGuid: string) => { + setOpportunity(newEncounterOpportunitySaveModel(configurationGuid)); + const entityInfo = await configurationService.getEntityInfo(configurationGuid); + const serviceLineInfo = await configurationService.getServiceLineInfo(configurationGuid); + setModalEntityProperties(entityInfo); + setModalServiceLineProperties(serviceLineInfo); + }; + if (userSetting.configurationGuid !== '') { + loadData(userSetting.configurationGuid); + } + }, [userSetting.configurationGuid]); + + useEffect(() => { + if (isVisible) { + resetForm(); + } + }, [isVisible]); + + const tick = async () => { + const status = await hangfireService.jobStatus(jobResults?.jobId); + const stateName = status.state.name; + switch (stateName) { + case 'Failed': + // Handles exceptions or unhandled validation from server + toast.show({ + key: name, + message: `${name} Opportunity was not created. Try again and contact us if the issue continues.`, + toastType: 'error' + }); + setWaiting(false); + break; + case 'Succeeded': + toast.show({ + key: name, + toastType: 'success', + duration: 0, + message: ( + <> + {name} +  Opportunity Created + + ) + }); + setWaiting(false); + break; + } + }; + + useInterval(tick, isWaiting ? delay : null); + + // Save form, clear fields + const handleFormFinish = (values: any) => { + handleOpportunitySave({ ...opportunity, ...values }); + }; + + const handleFinishFailed = (fields: any) => { + return; + }; + + const handleValuesChanged = (changedValues: any) => { + const mergedOpportunity = { ...opportunity, ...changedValues } as IEncounterOpportunitySaveModel; + + // if encounterGroup or opportunityLevel changed, we have to reset the selected encounterGroups or opportunityLevels + if (changedValues.encounterGroup != null) { + mergedOpportunity.encounterGroups = []; + form.resetFields(['encounterGroups']); + } + if (changedValues.opportunityLevel != null) { + mergedOpportunity.opportunityLevels = []; + form.resetFields(['opportunityLevels']); + } + + setOpportunity(mergedOpportunity); + form.setFieldsValue(mergedOpportunity); + }; + + const validationMessage = (message: string): boolean => { + // Handles exceptions or unhandled validation from server + if (message && message.length !== 0) { + if (message !== 'Enter a unique name') { + toast.show({ + key: name, + message: `${name} Opportunity was not created. Try again and contact us if the issue continues.`, + toastType: 'error' + }); + } + return true; + } + return false; + }; + + const getEncounterGroupFieldInfo = (): IScoreFieldInfo => { + if (opportunity.encounterGroup === EncounterGroup.PatientPopulation) { + return { + dimensionName: 'Patient Populations', + dimensionGuidOrGlobalId: 'Clinical Indicator', + modalTitle: 'Patient Populations' + }; + } else { + return { + dimensionName: 'Case Type', + dimensionGuidOrGlobalId: 'Case Type', + hierarchyGuid: 'c20b06b1-5a60-4fa5-825d-8e9a0524f783', + modalTitle: 'Case Types' + }; + } + }; + + const getOpportunityLevelFieldInfo = (): IScoreFieldInfo => { + switch (opportunity.opportunityLevel) { + case OpportunityLevelTypes.Entity: + return { + dimensionName: modalEntityProperties.entityLabel, + dimensionGuidOrGlobalId: modalEntityProperties.departmentRollupGlobalId, + modalTitle: modalEntityProperties.entityTitle + }; + case OpportunityLevelTypes.ServiceLine: + return { + dimensionName: modalServiceLineProperties.serviceLineName, + dimensionGuidOrGlobalId: modalServiceLineProperties.serviceLineDimensionGuid, + modalTitle: modalServiceLineProperties.serviceLineTitle + }; + case OpportunityLevelTypes.Specialty: + return { + dimensionName: 'DSS Physician Specialty', + dimensionGuidOrGlobalId: 'Physician Specialty', + label: 'Physician Specialty', + modalTitle: 'Physician Specialties' + }; + case OpportunityLevelTypes.Provider: + return { + dimensionName: 'Primary Physicians', + dimensionGuidOrGlobalId: 'Physician Primary', + modalTitle: 'Primary Physicians' + }; + default: + return { + dimensionName: '', + dimensionGuidOrGlobalId: '', + modalTitle: '' + }; + } + }; + + const handleModalClose = async () => { + if (onCancel) { + onCancel(); + } + resetForm(); + }; + + const getEncounterOpportunityUrl = (opportunityGuid: string) => { + return `${jazzUrl}CCI/Encounter/OpportunityViewer.aspx?opportunityGuid=${opportunityGuid}`; + }; + + const resetForm = async () => { + // clear custom validation style/message + const newOpportunity = newEncounterOpportunitySaveModel(userSetting.configurationGuid); + setOpportunity(newOpportunity); + form.setFieldsValue(newOpportunity); + }; + + const handleOpportunitySave = async (saveOpportunity: IEncounterOpportunitySaveModel) => { + setSaving(true); + setName(saveOpportunity.name); + try { + const results = await opportunityService.createOpportunity(saveOpportunity); + if (onAdd) { + onAdd(); + } + toast.show({ + key: name, + toastType: 'loading', + message: `Creating ${saveOpportunity.name} Opportunity`, + duration: 2 + }); + setJobResults(results); + setWaiting(true); + } catch (error) { + if (error instanceof Response) { + const responseBody = await error.json(); + if (validationMessage(responseBody.message)) { + if (responseBody.message !== 'Enter a unique name') { + handleModalClose(); + } + } + } + } + setSaving(false); + }; + + const validateName = (name: string) => { + return nameValidationService.nameIsValid(userSetting.configurationGuid, OpportunityTypes.Encounter, name); + }; + + const encounterGroupFieldInfo = getEncounterGroupFieldInfo(); + const opportunityLevelFieldInfo = getOpportunityLevelFieldInfo(); + + return ( + + + + + + + + + + } + > +
+ ({ + async validator(_, value) { + if (!value || (await validateName(value))) { + return Promise.resolve(); + } + return Promise.reject(nameIsDuplicate); + } + }) + ]} + > + + + + + + + + + + + + {opportunity.opportunityLevel !== undefined && opportunity.opportunityLevel !== OpportunityLevelTypes.Organization && ( + + + + )} + + + +
+
+ ); +}; + +export default EncounterOpportunityModal; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Filters.test.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Filters.test.tsx new file mode 100644 index 0000000..37f7ab5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Filters.test.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { render, screen, fireEvent } from '../../test-utils/testUtils'; +import { opportunityService } from './data/opportunityService'; +import Filters from './Filters'; +import { IEncounterFilterOptions, newFilterOptions } from './data/IEncounterFilterOptions'; +import { EncounterGroup, IEncounterFilters } from './data/IEncounterFilters'; +import { dropDownTestUtils } from '@strata/test-utils/lib'; +import { SeasonalityType } from '../../shared/data/SeasonalityType'; + +const mockFilterOptions: IEncounterFilterOptions = newFilterOptions({ + opportunityLevels: [ + { text: 'Opportunity Level 1', id: '1' }, + { text: 'Opportunity Level 2', id: '2' } + ] +}); + +const mockInitialFilters: IEncounterFilters = { + identifiedSavings: undefined, + cases: undefined, + encounterGroups: EncounterGroup.All, + opportunityLevels: [], + seasonality: SeasonalityType.WithAndWithout, + viewVisible: true, + excludeInitiatives: false, + opportunitySearch: '' +}; + +test('Filters default to empty', async () => { + jest.spyOn(opportunityService, 'getFilterOptions').mockImplementation(() => Promise.resolve(mockFilterOptions)); + + // render component + render( + { + return; + }} + onCancel={() => { + return; + }} + /> + ); + + // verify combo boxes appear with nothing selected + const opportunityLevelDropdown = dropDownTestUtils.getDropDown('Level'); + + // verify default value is set correctly + expect(dropDownTestUtils.getSelectedOption(opportunityLevelDropdown)).toEqual(''); +}); + +test('Filters clear', async () => { + jest.spyOn(opportunityService, 'getFilterOptions').mockImplementation(() => Promise.resolve(mockFilterOptions)); + + // render component + render( + { + return; + }} + onCancel={() => { + return; + }} + /> + ); + + // verify combo boxes appear with nothing selected + const opportunityLevelDropdown = dropDownTestUtils.getDropDown('Level'); + const identifiedSavingsInput = screen.getByRole('spinbutton', { name: 'Identified Savings Minimum' }); + const caseMinimumInput = screen.getByRole('spinbutton', { name: 'Case Minimum' }); + + // verify default value is set correctly + for (const txt of mockFilterOptions.opportunityLevels) { + await dropDownTestUtils.selectOption(opportunityLevelDropdown, txt.text); + } + fireEvent.change(identifiedSavingsInput, { target: { value: 123 } }); + fireEvent.change(caseMinimumInput, { target: { value: 123 } }); + + const label = `All ${mockFilterOptions.opportunityLevels.length} selected`; + expect(dropDownTestUtils.getSelectedOption(opportunityLevelDropdown)).toEqual(label); + expect(identifiedSavingsInput).toHaveValue('123'); + expect(caseMinimumInput).toHaveValue('123'); + + const clearButton = screen.getByRole('button', { name: 'Clear Filters' }); + fireEvent.click(clearButton); + + expect(dropDownTestUtils.getSelectedOption(opportunityLevelDropdown)).toEqual(''); + expect(identifiedSavingsInput).not.toHaveValue(); + expect(caseMinimumInput).not.toHaveValue(); +}); diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Filters.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Filters.tsx new file mode 100644 index 0000000..cb6f716 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Filters.tsx @@ -0,0 +1,122 @@ +import React, { useCallback } from 'react'; +import Button from '@strata/tempo/lib/button'; +import Form from '@strata/tempo/lib/form'; +import Drawer from '@strata/tempo/lib/drawer'; +import DropDown from '@strata/tempo/lib/dropdown'; +import RadioGroup from '@strata/tempo/lib/radiogroup'; +import InputNumber from '@strata/tempo/lib/inputnumber'; +import Spacing from '@strata/tempo/lib/spacing'; +import Checkbox from '@strata/tempo/lib/checkbox'; +import { initialFilters, IEncounterFilters, EncounterGroup } from './data/IEncounterFilters'; +import { useEffect, useState } from 'react'; +import { opportunityService } from './data/opportunityService'; +import { IListMember } from '../../shared/data/IListMember'; +import { useConfigurations } from '../../shared/configuration/ConfigurationContext'; +import { convertNullOrEmptyStrToUndefined } from '../../shared/utilities'; +import { SeasonalityType } from '../../shared/data/SeasonalityType'; + +interface IFiltersProps { + filters: IEncounterFilters; + isVisible: boolean; + onApply: (filters: IEncounterFilters) => void; + onCancel: () => void; +} + +const Filters: React.FC = (props: IFiltersProps) => { + const [opportunityLevels, setOpportunityLevels] = useState(); + const { currentConfiguration } = useConfigurations(); + + const [form] = Form.useForm(); + const handleClearFilters = useCallback(() => { + form.setFieldsValue(initialFilters); + }, [form]); + + useEffect(() => { + const loadFiltersOptions = async () => { + if (currentConfiguration == null) { + return; + } + const { opportunityLevels } = await opportunityService.getFilterOptions(currentConfiguration.configurationGuid); + + setOpportunityLevels(opportunityLevels); + handleClearFilters(); + }; + + loadFiltersOptions(); + }, [currentConfiguration, handleClearFilters]); + + const handleApplyFilters = () => { + const filters: IEncounterFilters = convertNullOrEmptyStrToUndefined(form.getFieldsValue()); + props.onApply(filters); + }; + + const handleCancel = () => { + props.onCancel(); + form.setFieldsValue(props.filters); + }; + + return ( + + + + + + + + } + visible={props.isVisible} + onClose={handleCancel} + > +
+ + + + + + + + + + + + + + + + + + + + Exclude Opportunities with Initiatives + +
+
+ ); +}; + +export default Filters; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Opportunities.test.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Opportunities.test.tsx new file mode 100644 index 0000000..3f1a85a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Opportunities.test.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { render, screen, within } from '../../test-utils/testUtils'; +import { IEncounterOpportunity, newEncounterOpportunity } from './data/IEncounterOpportunity'; +import { OpportunityLevelTypes } from '../../shared/opportunities/data/OpportunityLevelTypes'; +import { opportunityService } from './data/opportunityService'; +import { IPagedApiResponse } from '../../shared/paging/IPagedApiResponse'; +import Opportunities from './Opportunities'; + +const mockOpportunities: IEncounterOpportunity[] = [ + newEncounterOpportunity({ + opportunityKey: 'EN - 1', + name: 'Encounter 1', + identifiedSavings: 100, + cases: 10, + encounterGroup: 0, + opportunityLevel: OpportunityLevelTypes.Entity, + isSeasonal: true + }), + newEncounterOpportunity({ + opportunityKey: 'EN - 2', + name: 'Encounter 2', + identifiedSavings: 200, + cases: 20, + encounterGroup: 1, + opportunityLevel: OpportunityLevelTypes.Organization, + isSeasonal: false + }) +]; + +const mockPagedOpportunities: IPagedApiResponse = { + data: mockOpportunities, + pagingInfo: { + pageNumber: 1, + resultsPerPage: 25, + totalNumberOfResults: 2, + totalPages: 1 + } +}; + +test('All opportunities are shown', async () => { + jest.spyOn(opportunityService, 'getOpportunities').mockImplementation(() => Promise.resolve(mockPagedOpportunities)); + + // render component + render(); + + // wait for grid to load + const cell = await screen.findByRole('cell', { name: mockPagedOpportunities.data[0].opportunityKey }); + + // get rows + const tableBody = cell?.parentElement?.parentElement as HTMLElement; + const rows = within(tableBody).getAllByRole('row'); + + // verify data shows in grid + mockPagedOpportunities.data.forEach((opportunity, index) => { + const row = rows[index]; + within(row).getByText(opportunity.opportunityKey); + within(row).getByText(opportunity.name); + within(row).getByText(opportunity.opportunityLevel); + }); +}); diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Opportunities.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Opportunities.tsx new file mode 100644 index 0000000..5472b79 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/Opportunities.tsx @@ -0,0 +1,102 @@ +import { useJazzUrlWithVersion } from '@strata/core/lib'; +import { usePageLoader } from '@strata/tempo/lib'; +import Button from '@strata/tempo/lib/button/Button'; +import DataGrid from '@strata/tempo/lib/datagrid'; +import PlusIcon from '@strata/tempo/lib/icon/PlusIcon'; +import React, { useEffect, useState } from 'react'; +import { useHistory } from 'react-router'; +import { useConfigurations } from '../../shared/configuration/ConfigurationContext'; +import OpportunitiesGrid from '../../shared/opportunities/OpportunitiesGrid'; +import { IOpportunity } from '../../shared/opportunities/data'; +import Filters from './Filters'; +import { IEncounterFilters, initialFilters, numberOfFilters } from './data/IEncounterFilters'; +import { opportunityService } from './data/opportunityService'; + +const Opportunities: React.FC = () => { + const [filters, setFilters] = useState(initialFilters); + const [filtersVisible, setFiltersVisible] = useState(false); + const [isTempoOpportunityPageEnabled, setIsTempoOpportunityPageEnabled] = useState(); + const { currentConfiguration } = useConfigurations(); + const [currentConfigurationGuid, setCurrentConfigurationGuid] = useState(currentConfiguration?.configurationGuid ?? ''); + + const jazzUrl = useJazzUrlWithVersion(); + const history = useHistory(); + const { setLoading } = usePageLoader(); + + useEffect(() => { + document.title = 'Encounter Opportunities'; + setLoading(true); + opportunityService + .isOpportunityTempoPageEnabled() + .then((result: boolean) => { + setIsTempoOpportunityPageEnabled(result); + }) + .finally(setLoading(false)); + }, [setLoading]); + + useEffect(() => { + if (currentConfiguration == null) { + return; + } + setFilters(initialFilters); + setCurrentConfigurationGuid(currentConfiguration.configurationGuid); + }, [currentConfiguration]); + + const handleApplyFilters = (newFilters: IEncounterFilters) => { + setFiltersVisible(false); + setFilters({ ...filters, ...newFilters }); + }; + + const handleSearch = (searchValue: string) => { + setFilters((filters) => { + return { ...filters, opportunitySearch: searchValue }; + }); + }; + + const getOpportunityUrl = (opportunity: IOpportunity) => { + return isTempoOpportunityPageEnabled ? `/encounter-opportunity/${opportunity.opportunityGuid}` : `${jazzUrl}CCI/Encounter/OpportunityViewer.aspx?opportunityGuid=${opportunity.opportunityGuid}`; + }; + + const getInitiativeUrl = (opportunity: IOpportunity) => { + return `${jazzUrl}CCI/Encounter/InitiativeViewer.aspx?id=${opportunity.initiativeGuid}`; + }; + + const addOpportunityButton = ( + + ); + + return isTempoOpportunityPageEnabled === undefined ? null : ( + <> + setFiltersVisible(false)} /> + setFiltersVisible(true)} + onSearch={handleSearch} + customAction={addOpportunityButton} + > + + + + (args.rowData[args.field] === 'ServiceLine' ? 'Service Line' : args.rowData[args.field])} + /> + (args.rowData[args.field] ? 'With' : 'Without')} /> + + + ); +}; + +export default Opportunities; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterFilterOptions.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterFilterOptions.ts new file mode 100644 index 0000000..24ad24a --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterFilterOptions.ts @@ -0,0 +1,14 @@ +import { IListMember } from '../../../shared/data/IListMember'; + +export interface IEncounterFilterOptions { + opportunityLevels: IListMember[]; +} + +export const newFilterOptions = (data: Partial = {}): IEncounterFilterOptions => { + return { + ...{ + opportunityLevels: [] + }, + ...data + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterFilters.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterFilters.ts new file mode 100644 index 0000000..374bedb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterFilters.ts @@ -0,0 +1,39 @@ +import { SeasonalityType } from '../../../shared/data/SeasonalityType'; +import { IOpportunityFilters } from '../../../shared/opportunities/data'; + +export interface IEncounterFilters extends IOpportunityFilters { + cases?: number; + opportunityLevels: string[]; + encounterGroups: EncounterGroup; + seasonality: SeasonalityType; +} + +export enum EncounterGroup { + All = -1, + CaseType = 0, + PatientPopulation = 1, + FlexibleExplorationPatientPopulation = 2 +} + +export const initialFilters: IEncounterFilters = { + identifiedSavings: undefined, + cases: undefined, + encounterGroups: EncounterGroup.All, + opportunityLevels: [], + seasonality: SeasonalityType.WithAndWithout, + viewVisible: true, + excludeInitiatives: false, + opportunitySearch: '' +}; + +export const numberOfFilters = (filters: IEncounterFilters): number => { + let filtersCount = 0; + if (filters.identifiedSavings != null) filtersCount++; + if (filters.cases != null) filtersCount++; + if (filters.encounterGroups !== EncounterGroup.All) filtersCount++; + if (filters.opportunityLevels.length > 0) filtersCount++; + if (filters.seasonality !== SeasonalityType.WithAndWithout) filtersCount++; + if (filters.viewVisible === false) filtersCount++; + if (filters.excludeInitiatives === true) filtersCount++; + return filtersCount; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterOpportunity.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterOpportunity.ts new file mode 100644 index 0000000..9280b70 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterOpportunity.ts @@ -0,0 +1,73 @@ +import { IOpportunityDetails } from '../../../shared/opportunities/data/IOpportunity'; +import { EncounterGroup } from '../../../shared/opportunities/data/EncounterGroup'; +import { OpportunityLevelTypes } from '../../../shared/opportunities/data/OpportunityLevelTypes'; + +export interface IEncounterOpportunity extends IOpportunityDetails { + name: string; + cases: number; + opportunityId: number; + opportunityLevel: OpportunityLevelTypes; + encounterGroupIds: string[]; + opportunityLevelIds: string[]; + revenueGoal: number; + revenueImprovement: number; + costGoal: number; + costImprovement: number; + marginGoal: number; + marginImprovement: number; + gridData: IEncounterGridData | null; +} + +export const newEncounterOpportunity = (data: Partial = {}): IEncounterOpportunity => { + return { + ...{ + name: '', + opportunityKey: '', + opportunityId: 0, + opportunityGuid: '', + identifiedSavings: 0, + cases: 0, + encounterGroup: EncounterGroup.CaseType, + encounterGroups: [], + opportunityLevel: OpportunityLevelTypes.Organization, + isSeasonal: false, + hasInitiative: false, + note: '', + isHidden: false, + initiativeGuid: '', + sourceOpportunity: '', + sourceOpportunityId: -1, + revenueGoal: 0, + revenueImprovement: 0, + costGoal: 0, + costImprovement: 0, + marginGoal: 0, + marginImprovement: 0, + gridData: null, + baselineStartDate: new Date(), + baselineEndDate: new Date(), + trackingStartDate: new Date(), + trackingEndDate: new Date(), + chargeCodes: undefined, + encounterGroupIds: [], + opportunityLevelIds: [] + }, + ...data + }; +}; + +interface IEncounterGridData { + [key: string]: IEncounterGridRow[]; +} + +export interface IEncounterGridRow { + text: string; + levelId: number; + volume: number; + revenuePerCase: number; + revenueTotal: number; + costPerCase: number; + costTotal: number; + marginPerCase: number; + marginTotal: number; +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterOpportunitySaveModel.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterOpportunitySaveModel.ts new file mode 100644 index 0000000..e460a3f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterOpportunitySaveModel.ts @@ -0,0 +1,29 @@ +import { EncounterGroup } from '../../../shared/opportunities/data/EncounterGroup'; +import { OpportunityLevelTypes } from '../../../shared/opportunities/data/OpportunityLevelTypes'; + +export interface IEncounterOpportunitySaveModel { + opportunityGuid: string; + configurationGuid: string; + name: string; + encounterGroup: EncounterGroup; + opportunityLevel: OpportunityLevelTypes | undefined; + encounterGroups: []; + opportunityLevels: []; + seasonality: boolean; +} + +export const newEncounterOpportunitySaveModel = (configurationGuid: string, data: Partial = {}): IEncounterOpportunitySaveModel => { + return { + ...{ + opportunityGuid: '', + configurationGuid: configurationGuid, + name: '', + encounterGroup: EncounterGroup.CaseType, + opportunityLevel: undefined, + encounterGroups: [], + opportunityLevels: [], + seasonality: true + }, + ...data + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterOpportunitySaveModelChanges.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterOpportunitySaveModelChanges.ts new file mode 100644 index 0000000..946e109 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/IEncounterOpportunitySaveModelChanges.ts @@ -0,0 +1,13 @@ +export interface IEncounterOpportunitySaveModelChanges { + levelId: number; + volume: number; + revenuePerCase?: number; + costPerCase?: number; +} + +export const DefaultEncounterOpportunitySaveModelChanges: IEncounterOpportunitySaveModelChanges = { + levelId: 0, + volume: 0, + revenuePerCase: 0, + costPerCase: 0 +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/opportunityService.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/opportunityService.ts new file mode 100644 index 0000000..345e98e --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/encounter/opportunities/data/opportunityService.ts @@ -0,0 +1,92 @@ +import { appConfig, getSecureService } from '@strata/core/lib'; +import { IOpportunityJobResults } from '../../../shared/hangfire/IOpportunityJobResults'; +import { IOpportunityFilters, IOpportunityService } from '../../../shared/opportunities/data'; +import { IPagedApiResponse, IPagingOptions, ISortOptions } from '../../../shared/paging'; +import { IEncounterFilterOptions } from './IEncounterFilterOptions'; +import { IEncounterOpportunity } from './IEncounterOpportunity'; +import { IEncounterOpportunitySaveModel } from './IEncounterOpportunitySaveModel'; +import { IInitiative } from '../../../shared/initiatives/data/IInitiative'; +import { IEncounterOpportunitySaveModelChanges } from './IEncounterOpportunitySaveModelChanges'; + +const { httpGet, httpPost, httpPut, httpDelete, httpDownloadAndDisplayFile } = getSecureService(`${appConfig.apiUrl}v1/`); +const url = 'encounter-opportunities'; + +export const opportunityService: IOpportunityService = { + getOpportunities: async (configurationGuid: string, pagingOptions: IPagingOptions, filters: IOpportunityFilters): Promise> => { + return httpPost>(`encounter-opportunities`, { + pagingOptions, + filters: { + ...filters, + configurationGuid + } + }); + }, + + getOpportunity: async (opportunityGuid: string): Promise => { + return httpGet(`${url}/${opportunityGuid}`); + }, + + export: async (configurationGuid: string, sortOptions: ISortOptions, filters: IOpportunityFilters): Promise => { + const date = new Date(); + const fileName = 'Encounter_CI_Opportunities_' + date.toISOString().split('T')[0]; + httpDownloadAndDisplayFile( + 'encounter-opportunities/export', + { + method: 'post', + body: JSON.stringify({ + sortOptions, + filters: { + ...filters, + configurationGuid + } + }) + }, + fileName + ); + }, + + getFilterOptions: (configurationGuid: string): Promise => { + return httpGet(`encounter-opportunities/${configurationGuid}/filter-options`); + }, + + setHidden: async (opportunityGuid: string, isHidden: boolean): Promise => { + await httpPut(`encounter-opportunities/${opportunityGuid}/set-hidden`, isHidden); + }, + + setNote: async (opportunityGuid: string, note: string): Promise => { + await httpPut(`encounter-opportunities/${opportunityGuid}/set-note`, note); + }, + + createOpportunity: async (opportunity: IEncounterOpportunitySaveModel): Promise => { + return httpPost(`encounter-opportunities/create`, opportunity); + }, + + getInitiativeInfo: (opporturnityGuid: string): Promise => { + return httpGet(`${url}/${opporturnityGuid}/initiative-info`); + }, + + getById(opportunityGuid: string | number): Promise { + return httpGet(`${url}/${opportunityGuid}`); + }, + getEditModel(opportunityGuid: string | number): Promise { + return httpGet(`${url}/${opportunityGuid}/edit-model`); + }, + + isOpportunityTempoPageEnabled: async (): Promise => { + return httpGet(`${url}/tempo-page-enabled`); + }, + + updateGoals(opportunityGuid: string, changes: IEncounterOpportunitySaveModelChanges[]): Promise { + return httpPut(`${url}/${opportunityGuid}/update-goals`, changes); + }, + + delete: async (opportunityGuid: string, callback: () => void, failed: (err: any) => void): Promise => { + httpDelete(`${url}/${opportunityGuid}/delete`) + .then(callback) + .catch((e) => failed(e)); + }, + + updateEncounterOpportunity: async (opportunity: IEncounterOpportunity): Promise => { + return httpPut(`${url}/${opportunity.opportunityGuid}/update-opportunity`, opportunity); + } +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilyDetailGrid.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilyDetailGrid.tsx new file mode 100644 index 0000000..590659c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilyDetailGrid.tsx @@ -0,0 +1,512 @@ +// #region Imports + +// Third-party imports +import { isEmpty } from 'lodash'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useParams } from 'react-router'; + +// Strata Tempo UI imports +import Button from '@strata/tempo/lib/button/Button'; +import FixedActionBar from '@strata/tempo/lib/fixedactionbar/FixedActionBar'; +import RouteConfirm from '@strata/tempo/lib/routeconfirm/RouteConfirm'; +import Spacing from '@strata/tempo/lib/spacing/Spacing'; +import TreeGrid from '@strata/tempo/lib/treegrid'; + +// Internal imports - data +import { ChipKey } from '../shared/components/FilterBar/data/IFilterChipItem'; + +// Internal imports - components +import CaseTypeFamilyMetrics from './CaseTypeFamilyMetrics'; +import { BenchmarkingBannerText } from './components/BenchmarkingBannerText'; +import { buildTreeGrid } from './components/BuildTreeGrid'; + +// Internal imports - data & services +import { toggleGridResetKey } from './data/CaseTypeFamilyFunctions'; +import { ICaseTypeFamilyMetricDetail } from './data/ICaseTypeFamilyMetricDetail'; +import IExclusionCriteria from './data/IExclusionCriteria'; +import IFilter from './data/IFilter'; + +// Internal imports - hooks +import { useCaseTypeFamilyGridBase } from './hooks/useCaseTypeFamilyGridBase'; +import { useColumnManagement } from './hooks/useColumnManagement'; +import { useEncounterManagement } from './hooks/useEncounterManagement'; +import { useExplorationColumnConfigs } from './hooks/useExplorationColumnConfigs'; +import { useExplorationDataFetching } from './hooks/useExplorationDataFetching'; +import { TreeGridLevel, useTreeGridExpansion } from './hooks/useTreeGridExpansion'; + +// Internal imports - types +import { IExplorationPopulation } from './opportunities/data/IExplorationPopulation'; + +// #endregion + +// #region Constants + +// ============================================================================ +// Constants +// ============================================================================ + +const DEFAULT_SORT_FIELD = 'name'; +const GRID_NAME_PRIMARY = 'FlexibleExplorationDetails'; +const GRID_NAME_LINKED = 'FlexibleExplorationSummary'; +const COLUMNS_NOT_TO_HIDE = ['name', 'isExcluded']; + +const EXPANDER_LEVELS = [ + { text: 'Entity', value: TreeGridLevel.Entity }, + { text: 'Code', value: TreeGridLevel.Code }, + { text: 'Primary Physician', value: TreeGridLevel.Physician }, + { text: 'Encounter', value: TreeGridLevel.Encounter } +]; + +// #endregion + +// #region Types + +// ============================================================================ +// Types +// ============================================================================ + +/** + * Props for the CaseTypeFamilyDetailGrid component. + */ +interface ICaseTypeFamilyDetailGridProps { + /** Callback to update the case type family name in parent */ + setCaseTypeFamilyName: (updatedOpportunity: string) => void; + /** Callback to update metrics in parent */ + updateMetrics: (m: ICaseTypeFamilyMetricDetail) => void; + /** Currently applied encounter exclusions */ + encounterExclusionsApplied: number[]; + /** Callback to set applied encounter exclusions */ + setEncounterExclusionsApplied: (exclusions: number[]) => void; + /** Currently applied encounter inclusions */ + encounterInclusionsApplied: number[]; + /** Callback to set applied encounter inclusions */ + setEncounterInclusionsApplied: (inclusions: number[]) => void; + /** Callback to set encounter IDs used in mapping */ + setMappingEncounterIds: (mappings: number[]) => void; + /** Exploration population data when editing an existing population */ + explorationPopulationData: IExplorationPopulation | undefined; + /** Controls visibility of save populations UI */ + isModalsVisible: boolean; + /** Controls whether the grid is read-only */ + isGridReadOnly: boolean; + /** Controls visibility of the column layout manager */ + setColumnLayoutVisible: boolean; + /** Callback to hide the column layout manager */ + onHideColumnLayout: () => void; +} + +// #endregion + +// #region Component + +// ============================================================================ +// Component +// ============================================================================ + +/** + * CaseTypeFamilyDetailGrid displays detailed encounter-level data with: + * - Hierarchical tree grid with drill-down capability (Entity > Code > Physician > Encounter) + * - Manual encounter inclusion/exclusion management + * - Expandable level controls + * - Population metrics display + * - Column management and filtering capabilities + * - Route confirmation prompts for unsaved changes + */ +const CaseTypeFamilyDetailGrid: React.FC = ({ + setCaseTypeFamilyName, + encounterExclusionsApplied, + setEncounterExclusionsApplied, + updateMetrics, + setMappingEncounterIds, + encounterInclusionsApplied, + setEncounterInclusionsApplied, + explorationPopulationData, + isModalsVisible, + setColumnLayoutVisible, + isGridReadOnly, + onHideColumnLayout +}) => { + // #region Route Parameters + + // ======================================================================== + // Route Parameters + // ======================================================================== + + const { caseTypeFamilyId, costDriver, explorationPopulationId } = useParams<{ + caseTypeFamilyId: string; + costDriver: string; + explorationPopulationId: string; + }>(); + + // #endregion + + // #region Hooks + + // ======================================================================== + // Hooks - Base (shared logic) + // ======================================================================== + + const { + modal, + configuration, + loadingConfiguration, + firstLoading, + setFirstLoading, + gridResetKey, + setGridResetKey, + filters, + exclusionCriteria, + peerGroupFilters, + markDataAsLoaded, + renderFilterBar, + showBanner, + onDismissBanner, + showPeerGroupBenchmarking, + previousPeerGroupIdRef + } = useCaseTypeFamilyGridBase({ + explorationPopulationData, + explorationPopulationId, + isBaseOptionsReadOnly: isGridReadOnly, + onDrawerApply: () => { + hasAppliedFiltersRef.current = true; + setFirstLoading(false); + clearEncounterExclusionsInclusionsStates(); + } + }); + + // ======================================================================== + // Hooks - Data Fetching + // ======================================================================== + + // Allow auto-fetch if we have exploration population data (editing saved population) + // OR if we have URL parameters (navigated from summary grid) + useEffect(() => { + if (explorationPopulationData || (caseTypeFamilyId && costDriver)) { + hasAppliedFiltersRef.current = true; + } + }, [explorationPopulationData, caseTypeFamilyId, costDriver]); + + const { + encounterGroupDetails, + metrics, + caseTypeFamilyType, + isExplorationInformationPageEnabled, + loading, + fetchData: fetchDataFromHook + } = useExplorationDataFetching({ + configuration, + caseTypeFamilyId, + costDriver, + explorationPopulationData, + peerGroupFilters: peerGroupFilters || { peerGroupId: 0, name: '', entityTypes: [], censusRegions: [], operatingExpenses: [], amcTypes: [], bedSize: [], urbanRural: [] }, + updateMetrics, + setCaseTypeFamilyName, + // eslint-disable-next-line @typescript-eslint/no-empty-function + setSummaryFilters: () => {} // Not used in detail grid + }); + + // ======================================================================== + // Hooks - Tree Grid Expansion + // ======================================================================== + + const { treeGridKeysData, expandedLevel, updateExpandedLevels } = useTreeGridExpansion({ + encounterGroupDetails + }); + + // ======================================================================== + // Hooks - Encounter Management + // ======================================================================== + + const { + encounterExclusions, + encounterInclusions, + isApplyExclusionActionBarVisible, + isEncounterIncluded, + handleSetEncounterExclusion, + setInitialEncounterExclusions, + handleApplyExclusions, + handleCancelApplyExclusions, + clearEncounterExclusionsInclusionsStates + } = useEncounterManagement({ + encounterExclusionsApplied, + setEncounterExclusionsApplied, + encounterInclusionsApplied, + setEncounterInclusionsApplied, + setMappingEncounterIds, + explorationPopulationData, + updateExpandedLevels + }); + + // ======================================================================== + // Hooks - Custom + // ======================================================================== + + const hiddenFilterChips = useMemo(() => [ChipKey.CaseTypeFamilyID, ChipKey.CostDriver, ChipKey.TopN], []); + + const { getColumnConfigs } = useExplorationColumnConfigs({ + viewType: 'detail', + detailConfig: { + caseTypeFamilyType, + isExplorationInformationPageEnabled, + caseTypeFamilyId, + costDriver, + encounterExclusions, + encounterInclusions, + isEncounterIncluded, + handleSetEncounterExclusion + }, + isIncludedInPopulationColumnReadOnly: isGridReadOnly + }); + + const { getColumnsWithVisibility, renderColumnManagerPanel } = useColumnManagement({ + gridName: GRID_NAME_PRIMARY, + additionalGridNames: [GRID_NAME_LINKED], + getColumnConfigs, + showPeerGroupBenchmarking, + columnManagerPanel: { + columnsNotToHide: COLUMNS_NOT_TO_HIDE + } + }); + + // ======================================================================== + // Refs + // ======================================================================== + + const initialDataLoadedRef = useRef(false); + const hasAppliedFiltersRef = useRef(false); + + // ======================================================================== + // Callbacks - Data fetching + // ======================================================================== + + /** + * Core data fetching logic. Extracted to avoid unstable dependencies. + * Takes all required data as parameters instead of closing over state. + */ + const executeDataFetch = useCallback( + async (currentFilters: IFilter, currentExclusionCriteria: IExclusionCriteria, currentEncounterExclusions: number[], currentEncounterInclusions: number[]): Promise => { + const freshEncounterGroupDetails = await fetchDataFromHook(currentExclusionCriteria, currentFilters, currentEncounterExclusions, currentEncounterInclusions); + + if (freshEncounterGroupDetails) { + setInitialEncounterExclusions(freshEncounterGroupDetails); + } + setGridResetKey((prev: number) => toggleGridResetKey(prev)); + updateExpandedLevels(TreeGridLevel.Entity); + }, + [fetchDataFromHook, setInitialEncounterExclusions, setGridResetKey, updateExpandedLevels] + ); + + // Ref storage for callbacks to avoid stale closures + // Store latest executeDataFetch in ref to avoid dependency issues + const executeDataFetchRef = useRef(executeDataFetch); + useEffect(() => { + executeDataFetchRef.current = executeDataFetch; + }); + + // Store latest callbacks in refs + const markDataAsLoadedRef = useRef(markDataAsLoaded); + useEffect(() => { + markDataAsLoadedRef.current = markDataAsLoaded; + }); + + // ======================================================================== + // Callbacks - Event handlers + // ======================================================================== + + /** + * Handles changes to encounters filters with confirmation prompt if manual changes exist. + * @param resolve - Callback to execute if user confirms the action + */ + const handleChangeEncountersFilters = useCallback( + (resolve: () => void): void => { + const hasManualChanges = !isEmpty(encounterExclusions) || !isEmpty(encounterInclusions) || !isEmpty(encounterExclusionsApplied) || !isEmpty(encounterInclusionsApplied); + + if (hasManualChanges) { + modal.confirmChanges({ + title: 'Remove manually changed encounters?', + content: Changing refinement options will remove manually changed encounters from the population., + okText: 'Continue', + cancelText: 'Cancel', + onOk() { + resolve(); + } + }); + } else { + resolve(); + } + }, + [encounterExclusions, encounterInclusions, encounterExclusionsApplied, encounterInclusionsApplied, modal] + ); + + // #endregion + + // #region Effects + + // ======================================================================== + // Effects + // ======================================================================== + // Stable serialized keys for deep comparison + const filtersKey = useMemo(() => JSON.stringify(filters), [filters]); + const exclusionCriteriaKey = useMemo(() => JSON.stringify(exclusionCriteria), [exclusionCriteria]); + const hasExplorationPopulationData = !!explorationPopulationData; + // Fetch data when filters, peer group, or applied encounter changes occur + useEffect(() => { + // Early exit if required data not ready + if (!filters || !exclusionCriteria || !peerGroupFilters || loadingConfiguration || !configuration) { + return; + } + + // Check if we should fetch + const canFetch = hasAppliedFiltersRef.current; + const hasRequiredContext = !!(caseTypeFamilyId && costDriver) || !!explorationPopulationData; + + if (!canFetch || !hasRequiredContext) { + if (firstLoading) setFirstLoading(false); + return; + } + + // Prevent duplicate fetches during peer group initialization + if (!initialDataLoadedRef.current && previousPeerGroupIdRef.current !== peerGroupFilters.peerGroupId) { + previousPeerGroupIdRef.current = peerGroupFilters.peerGroupId; + } + + // Execute fetch with current values + const doFetch = async () => { + await executeDataFetchRef.current(filters, exclusionCriteria, encounterExclusions, encounterInclusions); + + if (firstLoading) { + setFirstLoading(false); + markDataAsLoadedRef.current(); + initialDataLoadedRef.current = true; + } + }; + + doFetch(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + filtersKey, + exclusionCriteriaKey, + peerGroupFilters?.peerGroupId, + encounterExclusionsApplied, + encounterInclusionsApplied, + caseTypeFamilyId, + costDriver, + hasExplorationPopulationData, + loadingConfiguration, + configuration?.configurationGuid, + firstLoading + ]); + + // #endregion + + // #region Memoized Values + + // ======================================================================== + // Memoized Values + // ======================================================================== + + const columnConfigs = useMemo(() => getColumnsWithVisibility(), [getColumnsWithVisibility]); + + const { columns, headerGroup } = useMemo(() => buildTreeGrid(columnConfigs, { isDetailPage: true }), [columnConfigs]); + + const encounterChangePromptCondition = useMemo( + () => + !explorationPopulationId && !isModalsVisible && (!isEmpty(encounterExclusions) || !isEmpty(encounterInclusions) || !isEmpty(encounterExclusionsApplied) || !isEmpty(encounterInclusionsApplied)), + [explorationPopulationId, isModalsVisible, encounterExclusions, encounterInclusions, encounterExclusionsApplied, encounterInclusionsApplied] + ); + + const editSessionPromptCondition = useMemo(() => !!explorationPopulationId && !isModalsVisible, [explorationPopulationId, isModalsVisible]); + + // #endregion + + // #region Render + + // ======================================================================== + // Render + // ======================================================================== + + return ( + <> + {/* Route confirmation prompts */} + Leaving this page will remove manually changed encounters from the population.} + okText='Continue' + cancelText='Cancel' + /> + Leaving this page will stop edit session of the {explorationPopulationData?.name}. All unsaved changes will be lost.} + okText='Continue' + cancelText='Cancel' + /> + + {/* Benchmarking banner */} + + + {/* Filter bar with expandable level controls */} + {renderFilterBar({ + excludedChips: hiddenFilterChips, + hiddenChips: hiddenFilterChips, + expandableLevels: EXPANDER_LEVELS, + expandedLevelPos: expandedLevel, + setExpandedLevelPos: updateExpandedLevels, + onChangeEncounters: handleChangeEncountersFilters, + hideTopNFilter: true + })} + + {/* Population metrics display */} + + + {/* Tree grid with hierarchical data */} + + {columns} + + + {/* Column manager panel */} + {renderColumnManagerPanel({ + isVisible: setColumnLayoutVisible, + onCancel: onHideColumnLayout, + onApplyComplete: onHideColumnLayout + })} + + {/* Fixed action bar for applying/canceling encounter changes */} + + + + + } + /> + + ); +}; + +// #endregion + +// #endregion + +export default CaseTypeFamilyDetailGrid; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilyInformationGrid.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilyInformationGrid.tsx new file mode 100644 index 0000000..d2ac6b9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilyInformationGrid.tsx @@ -0,0 +1,374 @@ +import ActionBar from '@strata/tempo/lib/actionbar/ActionBar'; +import Button from '@strata/tempo/lib/button/Button'; +import Checkbox from '@strata/tempo/lib/checkbox/Checkbox'; +import DataHeaderCell from '@strata/tempo/lib/dataheader/DataHeaderCell'; +import DataHeaderGroup from '@strata/tempo/lib/dataheader/DataHeaderGroup'; +import DataHeaderRow from '@strata/tempo/lib/dataheader/DataHeaderRow'; +import { DropDownValue } from '@strata/tempo/lib/dropdown/DropDown'; +import FixedActionBar from '@strata/tempo/lib/fixedactionbar/FixedActionBar'; +import LayoutGrid from '@strata/tempo/lib/layoutgrid/LayoutGrid'; +import { usePageLoader } from '@strata/tempo/lib/pageloader/usePageLoader'; +import Text from '@strata/tempo/lib/text'; +import Tooltip from '@strata/tempo/lib/tooltip/Tooltip'; +import TreeGrid from '@strata/tempo/lib/treegrid/TreeGrid'; +import dayjs from 'dayjs'; +import { isEmpty, xor } from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router'; +import { useSessionStorage } from 'usehooks-ts'; +import { ChipKey, IFilterChipItem } from '../shared/components/FilterBar/data/IFilterChipItem'; +import FilterBar from '../shared/components/FilterBar/FilterBar'; +import { useConfigurations } from '../shared/configuration/ConfigurationContext'; +import CaseTypeFamilyMetrics from './CaseTypeFamilyMetrics'; +import Filters from './components/Filters'; +import { setDateStates } from './data/CaseTypeFamilyFunctions'; +import { explorationService } from './data/explorationService'; +import { ICaseTypeFamilyMetricDetail, newMetricDetail } from './data/ICaseTypeFamilyMetricDetail'; +import { IEncounterGroupDetailTreeGridData } from './data/IEncounterGroupDetail'; +import IExclusionCriteria, { newExclusionCriteria, parseCostTrimValue, parseLosCostSelection, parseLosTrimValue } from './data/IExclusionCriteria'; +import IFilter, { defaultFilter, getSessionFilter } from './data/IFilter'; + +interface ICaseTypeFamilyInformationGrid { + setCaseTypeFamilyName: (updatedOpportunity: string) => void; + setColumnName: (columnName: string) => void; +} + +const CaseTypeFamilyInformationGrid: React.FC = (props) => { + const { setCaseTypeFamilyName, setColumnName } = props; + const { caseTypeFamilyId, costDriver, column } = useParams<{ caseTypeFamilyId: string; costDriver: string; column: string }>(); + const [loading, setLoading] = React.useState(false); + const [firstLoading, setFirstLoading] = React.useState(true); + const [isApplyExclusionActionBarVisible, setIsApplyExclusionActionBarVisible] = React.useState(); + const [pendingEncounterExclusions, setPendingEncounterExclusions] = React.useState([]); + const [gridEncounterExclusions, setGridEncounterExclusions] = React.useState([]); + const [queryEncounterExclusions, setQueryEncounterExclusions] = React.useState([]); + const [exclusionCriteria, setExclusionCriteria] = React.useState(newExclusionCriteria()); + const [filters, setFilters] = React.useState(); + const [groupInformations] = React.useState(); + const [metrics] = React.useState(newMetricDetail); + const [treeGridKeysData] = React.useState([]); + const [dateStart, setDateStart] = useState(dayjs()); + const [dateEnd, setDateEnd] = useState(dayjs()); + + const [summaryFilters, setSummaryFilters] = useSessionStorage('summaryFilters', defaultFilter); + const [costOutlierValue, setCostOutlierValue] = useSessionStorage('costOutlierValue', ''); + const [losOutlierValue, setLosOutlierValue] = useSessionStorage('losOutlierValue', ''); + + const { currentConfiguration: configuration } = useConfigurations(); + const { setLoading: setLoadingPage } = usePageLoader(); + const hiddenFilterChips = [ChipKey.CaseTypeFamilyID, ChipKey.CostDriver]; + + // Simple outlier initialization + const setOutliersFromSession = (): void => { + const costSelection = parseLosCostSelection(costOutlierValue); + const losSelection = parseLosCostSelection(losOutlierValue); + setExclusionCriteria({ + ...exclusionCriteria, + losExclusionType: losSelection.exclusionType, + losExclusionValue: losSelection.value, + costExclusionType: costSelection.exclusionType, + costExclusionValue: costSelection.value + }); + }; + + // Simple data fetching + const fetchData = async () => { + if (configuration && filters) { + !firstLoading && setLoading(true); + try { + const filterChipItems = filters.filterChipItems.filter((item: IFilterChipItem) => !hiddenFilterChips.includes(item.key)); + + const detailInfo = await explorationService.getCaseTypeFamilyDetails( + configuration.configurationGuid, + caseTypeFamilyId, + costDriver, + queryEncounterExclusions, + [], + exclusionCriteria, + { + ...filters, + filterChipItems: filterChipItems + }, + null, // codeType + 0 // peerGroupId + ); + + setCaseTypeFamilyName(detailInfo.caseTypeFamilyName); + setColumnName(column); + } catch { + //prevent double error modal + } finally { + if (firstLoading) setFirstLoading(false); + else setLoading(false); + } + } + }; + + // Simple initialization effect + useEffect(() => { + if (configuration) { + setOutliersFromSession(); + if (!filters || !filters.filterChipItems) { + const sessionFilters = getSessionFilter(configuration, summaryFilters); + setDateStates(sessionFilters, setDateStart, setDateEnd); + setSummaryFilters(sessionFilters); + setFilters(sessionFilters); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [configuration]); + + useEffect(() => { + setLoadingPage(firstLoading); + }, [firstLoading, setLoadingPage]); + + // Simple data fetching effect + useEffect(() => { + if (configuration && filters) { + fetchData(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [queryEncounterExclusions, exclusionCriteria, filters, configuration]); + + useEffect(() => { + //using lodash to check if these two arrays are equal regardless of order + setIsApplyExclusionActionBarVisible(!isEmpty(xor(gridEncounterExclusions, pendingEncounterExclusions))); + }, [gridEncounterExclusions, pendingEncounterExclusions]); + + useEffect(() => { + setInitialEncounterExclusions(groupInformations ?? []); + }, [groupInformations]); + + const handleALOSChange = (value: DropDownValue) => { + // handle Average Length of Stay exclusions + const selection = parseLosCostSelection(value.toString()); + setLosOutlierValue(value.toString()); + setExclusionCriteria({ ...exclusionCriteria, ...{ losExclusionType: selection.exclusionType, losExclusionValue: selection.value } }); + resetEncounterExclusionCheckboxes(); + setQueryEncounterExclusions([]); + }; + + const handleTDVCChange = (value: DropDownValue) => { + // handle Total Direct Variable Cost exclusions + const selection = parseLosCostSelection(value.toString()); + setCostOutlierValue(value.toString()); + setExclusionCriteria({ ...exclusionCriteria, ...{ costExclusionType: selection.exclusionType, costExclusionValue: selection.value } }); + resetEncounterExclusionCheckboxes(); + setQueryEncounterExclusions([]); + }; + + // Simple encounter exclusion initialization + const setInitialEncounterExclusions = (groupDetails: IEncounterGroupDetailTreeGridData[]) => { + if (groupDetails) { + const excludedEncounters: number[] = []; + groupDetails + ?.flatMap((x) => x.children.flatMap((y) => y.children.flatMap((z) => z.children))) + .forEach((detail) => { + if (detail.data.isExcluded) excludedEncounters.push(detail.data.id); + }); + setPendingEncounterExclusions(excludedEncounters); + setGridEncounterExclusions(excludedEncounters); + } + }; + + // Simple encounter exclusion handler + const handleSetEncounterExclusion = (key: number, isIncluded: boolean) => { + const index = pendingEncounterExclusions.indexOf(key); + if (!isIncluded && index === -1) { + setPendingEncounterExclusions([...pendingEncounterExclusions, ...[key]]); + } else if (isIncluded && index !== -1) { + setPendingEncounterExclusions([...pendingEncounterExclusions.slice(0, index), ...pendingEncounterExclusions.slice(index + 1)]); + } + // set flag for target encounter for grid display check/uncheck + const targetEncounter = groupInformations?.flatMap((x) => x.children.flatMap((y) => y.children.flatMap((z) => z.children))).find((x) => x.key === key.toString()); + if (targetEncounter) { + targetEncounter.data.isExcluded = !isIncluded; + } + }; + + const handleApplyExclusions = () => { + setQueryEncounterExclusions(pendingEncounterExclusions); + setIsApplyExclusionActionBarVisible(false); + }; + + const handleCancelApplyExclusions = () => { + setIsApplyExclusionActionBarVisible(false); + resetEncounterExclusionCheckboxes(); + }; + + const resetEncounterExclusionCheckboxes = () => { + setPendingEncounterExclusions(gridEncounterExclusions); + + // reset all encounter exclusion checkboxes to what was last loaded + groupInformations + ?.flatMap((x) => x.children.flatMap((y) => y.children.flatMap((z) => z.children))) + .forEach((encounter) => { + if (gridEncounterExclusions.includes(encounter.key as unknown as number)) { + encounter.data.isExcluded = true; + } else { + encounter.data.isExcluded = false; + } + }); + }; + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + > + + (!rowData.children ? handleSetEncounterExclusion(rowData.key, e.target.checked)} /> : <>)} + /> + + (rowData.children ? <>{rowData.data.totalEncounters} : <>)} /> + (rowData.children ? <>{rowData.data.totalVariationEncounters} : <>)} + /> + + + (rowData.children ? <>{rowData.data.includedEncounterCount} : <>)} + /> + + rowData.children ? ( + <>{(rowData.data.mortalityRate * 100).toFixed(0)}% + ) : ( + <> + { + + {rowData.data.dischargeStatusCode} + + } + + ) + } + /> + + + + (rowData.children ? <>{rowData.data.includedEncountersBelowMedian} : <>)} + /> + (rowData.children ? <>{rowData.data.includedEncountersAboveMedian} : <>)} + /> + + + (rowData.children ? <>{rowData.data.excludedEncountersAboveMedian} : <>)} + /> + + + + + + + + } + /> + + ); +}; +export default CaseTypeFamilyInformationGrid; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilyMetrics.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilyMetrics.tsx new file mode 100644 index 0000000..c0853db --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilyMetrics.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import MetricGroup from '@strata/tempo/lib/metricgroup/MetricGroup'; +import { ICaseTypeFamilyMetricDetail } from './data/ICaseTypeFamilyMetricDetail'; +import Spacing from '@strata/tempo/lib/spacing/Spacing'; + +const CaseTypeFamilyMetrics: React.FC = (props: ICaseTypeFamilyMetricDetail) => { + return ( + + + + ); +}; + +export default CaseTypeFamilyMetrics; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilySummaryGrid.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilySummaryGrid.tsx new file mode 100644 index 0000000..ccf35d7 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/CaseTypeFamilySummaryGrid.tsx @@ -0,0 +1,863 @@ +// #region Imports + +// Third-party imports +import React, { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; + +// Strata Tempo UI imports +import Accordion from '@strata/tempo/lib/accordion'; +import Card from '@strata/tempo/lib/card/Card'; +import { IDataGridSortEvent } from '@strata/tempo/lib/datagrid/DataGrid'; +import RadioGroup from '@strata/tempo/lib/radiogroup'; +import Text from '@strata/tempo/lib/text/Text'; +import TreeGrid, { ITreeGridExpandEvent } from '@strata/tempo/lib/treegrid'; + +// Internal imports - utilities +import { ChipKey } from '../shared/components/FilterBar/data/IFilterChipItem'; +import { getErrorData, getErrorMessage } from '../shared/error/utils'; + +// Internal imports - components +import { BenchmarkingBannerText } from './components/BenchmarkingBannerText'; +import BenchmarkingHeatMapGrid from './components/BenchmarkingGridHeatMap'; +import { BenchmarkingLegendPopover } from './components/BenchmarkingLegendView'; +import { buildTreeGrid } from './components/BuildTreeGrid'; + +// Internal imports - data & services +import { BenchmarkingInsightsGranularity } from './data/BenchmarkingInsightsGranularity'; +import { applyTopNFilter, getTopNSortField, shouldFetchData, toggleGridResetKey } from './data/CaseTypeFamilyFunctions'; +import { explorationService } from './data/explorationService'; +import { IEncounterGroupTreeGridData } from './data/IEncounterGroupSummary'; +import { IHeatMapData } from './data/IHeatMapData'; + +// Internal imports - hooks +import { useCaseTypeFamilyGridBase } from './hooks/useCaseTypeFamilyGridBase'; +import { useColumnManagement } from './hooks/useColumnManagement'; +import { useExplorationColumnConfigs } from './hooks/useExplorationColumnConfigs'; +import { useScrollManagement } from './hooks/useScrollManagement'; + +// #endregion + +// #region Constants + +// ============================================================================ +// Constants +// ============================================================================ + +const MemoBenchmarkingHeatMapGrid = memo(BenchmarkingHeatMapGrid); + +const DEFAULT_SORT_FIELD = 'name'; +const DEFAULT_SORT_ORDER = 1; +const GRID_SPACING = 12; +const GRID_PADDING = '12px 12px'; +const CONTAINER_BOTTOM_GAP = 0; +// Accounts for the Card component's 1px top border + 1px bottom border (Ant Design default). +// Without this offset the scroll body would be 2px too tall, causing a hairline overflow. +const CARD_BORDER_WIDTH = 2; +const GRID_NAME_PRIMARY = 'FlexibleExplorationSummary'; +const GRID_NAME_LINKED = 'FlexibleExplorationDetails'; +const UNINITIALIZED_CHILD_KEY = '2-'; + +// #endregion + +// #region Types + +// ============================================================================ +// Types +// ============================================================================ + +/** + * Props for the CaseTypeFamilySummaryGrid component. + */ +interface ICaseTypeFamilySummaryGridProps { + /** Controls visibility of the column layout manager */ + setColumnLayoutVisible: boolean; + /** Callback to hide the column layout manager */ + onHideColumnLayout: () => void; +} + +// #endregion + +// #region Component + +// ============================================================================ +// Component +// ============================================================================ + +/** + * CaseTypeFamilySummaryGrid displays a summary view of case type families with: + * - Hierarchical tree grid with expandable cost driver details + * - Optional benchmarking heatmap when peer group is selected + * - Column management and filtering capabilities + * - Integration with exploration population data + */ +const CaseTypeFamilySummaryGrid: React.FC = ({ setColumnLayoutVisible, onHideColumnLayout }) => { + // #region Hooks + + // ======================================================================== + // Hooks - Base (shared logic) + // ======================================================================== + + const { + modal, + loading, + setLoading, + firstLoading, + setFirstLoading, + configuration, + benchmarking, + gridResetKey, + setGridResetKey, + manageColumnsVisible, + setManageColumnsVisible, + filters, + exclusionCriteria, + peerGroupFilters, + markDataAsLoaded, + renderFilterBar, + showBanner, + onDismissBanner, + showPeerGroupBenchmarking, + handlePeerGroupChange, + previousPeerGroupIdRef, + featureFlags, + granularity, + setGranularity + } = useCaseTypeFamilyGridBase({ + onDrawerApply: () => { + hasAppliedFiltersRef.current = true; + setFirstLoading(false); + }, + isBaseOptionsReadOnly: false + }); + + const isCodeLevelEnhancementEnabled = featureFlags.isEnabled('cibenchmarkinginsightscodelevelenhancement'); + + // ======================================================================== + // Hooks - State (component-specific) + // ======================================================================== + + // Grid data state + const [encounterGroupSummaries, setEncounterGroupSummaries] = useState(); + const [expandedRowKeys, setExpandedRowKeys] = useState([]); + + // Sort state + const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); + const [sortOrder, setSortOrder] = useState(DEFAULT_SORT_ORDER); + const [heatMapNameOrder, setHeatMapNameOrder] = useState | null>(null); + + // Heatmap state + const [heatMapData, setHeatMapData] = useState(); + const [heatMapLoading, setHeatMapLoading] = useState(false); + const [showLegend, setShowLegend] = useState(false); + const [activeAccordionKeys, setActiveAccordionKeys] = useState(['1']); + const [treeGridScrollHeight, setTreeGridScrollHeight] = useState(undefined); + + // ======================================================================== + // Hooks - Refs + // ======================================================================== + + const containerRef = useRef(null); + const accordionRef = useRef(null); + const treeGridWrapperRef = useRef(null); + const rafIdRef = useRef(null); + const hasHandledFirstLoadRef = useRef(false); + const initialDataLoadedRef = useRef(false); + const hasAppliedFiltersRef = useRef(false); + const reloadHandledRef = useRef(false); + const encounterGroupSummariesRef = useRef(); + + // ======================================================================== + // Hooks - Custom + // ======================================================================== + + const scrollToRow = useScrollManagement(); + + const { getColumnConfigs } = useExplorationColumnConfigs({ + viewType: 'summary' + }); + + const { getColumnsWithVisibility, renderColumnManagerPanel: renderColumnManagerDialog } = useColumnManagement({ + gridName: GRID_NAME_PRIMARY, + additionalGridNames: [GRID_NAME_LINKED], + getColumnConfigs, + showPeerGroupBenchmarking + }); + + // #endregion + + // #region Callbacks + + // ======================================================================== + // Callbacks - Data fetching + // ======================================================================== + + /** + * Fetches case type family summaries based on current filter configuration. + * Resets expanded rows and grid state on successful fetch. + * Returns the fetched summaries for use by caller. + */ + const fetchGridData = useCallback(async (): Promise => { + if (!configuration || !filters || !exclusionCriteria || !peerGroupFilters) { + return []; + } + + setLoading(true); + + try { + const summaries = await explorationService.getCaseTypeFamilySummaries(configuration.configurationGuid, exclusionCriteria, filters, peerGroupFilters.peerGroupId); + + setEncounterGroupSummaries(summaries); + encounterGroupSummariesRef.current = summaries; + setExpandedRowKeys([]); + setGridResetKey((prev: number) => toggleGridResetKey(prev)); + + // Mark that initial data has been loaded (used for peer group change detection) + if (!initialDataLoadedRef.current) { + initialDataLoadedRef.current = true; + } + + return summaries; + } catch (error) { + const errorMessage = getErrorMessage(await getErrorData(error, { 500: undefined })); + + if (errorMessage) { + modal.alert({ + title: 'Unable to load data', + content: errorMessage, + alertType: 'error' + }); + } + return []; + } finally { + if (firstLoading) { + setFirstLoading(false); + markDataAsLoaded(); + } + setLoading(false); + } + }, [configuration, filters, exclusionCriteria, peerGroupFilters, firstLoading, setLoading, setFirstLoading, modal, setGridResetKey, markDataAsLoaded]); + + /** + * Fetches raw heatmap data from API. + * Does not apply filtering - that is handled by fetchData after both grid and heatmap load. + */ + const fetchHeatMapData = useCallback(async () => { + if (!configuration || !filters || !exclusionCriteria || !peerGroupFilters || peerGroupFilters.peerGroupId === 0 || !benchmarking) { + return []; + } + + try { + const data = await explorationService.getHeatMapSummary(configuration.configurationGuid, exclusionCriteria, filters, peerGroupFilters.peerGroupId, granularity); + return data; + } catch (error) { + const errorMessage = getErrorMessage(await getErrorData(error, { 500: undefined })); + + if (errorMessage) { + modal.alert({ + title: 'Unable to load heatmap data', + content: errorMessage, + alertType: 'error' + }); + } + return []; + } + }, [configuration, filters, exclusionCriteria, peerGroupFilters, benchmarking, granularity, modal]); + + /** + * Fetches grid data and optionally heatmap data. + * Determines if heatmap should be fetched based on benchmarking and peer group settings. + * When both are needed, they're fetched in parallel. Once complete, filters heatmap to only show case type families in the grid data. + */ + const fetchData = useCallback(async () => { + const shouldFetchHeatmap = benchmarking && (peerGroupFilters?.peerGroupId ?? 0) !== 0; + + if (!shouldFetchHeatmap) { + await fetchGridData(); + return; + } + + setHeatMapLoading(true); + try { + // Fetch both grid and heatmap data in parallel + const [gridData, heatMapGridData] = await Promise.all([fetchGridData(), fetchHeatMapData()]); + + // Clear heatmap data if no grid data is returned + if (!gridData || gridData.length === 0) { + setHeatMapData(undefined); + return; + } + + // Keep only customer case type families and preserve the same order as the grid data. + // IHeatMapData is flat (one record per CTF × cost driver), so we filter + sort rather than + // map by name (which would collapse multiple cost driver records into one). + const gridOrderByName = new Map(gridData.map((s, i) => [s.data.name, i])); + const filteredData = (heatMapGridData ?? []).filter((heatMapRow) => gridOrderByName.has(heatMapRow.name)).sort((a, b) => (gridOrderByName.get(a.name) ?? 0) - (gridOrderByName.get(b.name) ?? 0)); + + setHeatMapData(filteredData); + } finally { + setHeatMapLoading(false); + } + }, [benchmarking, peerGroupFilters, fetchGridData, fetchHeatMapData]); + + /** + * Loads cost driver children for a specific case type family node. + * Updates only the children to preserve peer data in parent node. + */ + const loadCostDriverChildren = useCallback( + async (id: string): Promise => { + if (!configuration || !filters?.filterChipItems || !exclusionCriteria || !peerGroupFilters) { + return; + } + + setLoading(true); + + try { + const costDriverData = await explorationService.getCaseTypeFamilyCostDriverInfo(configuration.configurationGuid, id, exclusionCriteria, filters, peerGroupFilters.peerGroupId); + + const expandedNodeData = costDriverData[0]; + + setEncounterGroupSummaries((nodes) => nodes?.map((node) => (node.key === expandedNodeData.key ? { ...node, children: expandedNodeData.children } : node))); + } finally { + setLoading(false); + } + }, + [configuration, filters, exclusionCriteria, peerGroupFilters, setLoading] + ); + + // ======================================================================== + // Callbacks - Event handlers + // ======================================================================== + + /** + * Handles tree node expansion. Loads cost driver children if not already loaded. + */ + const handleNodeExpand = useCallback( + async (e: ITreeGridExpandEvent): Promise => { + const expandedNode = { ...e.node }; + + // Check if children are already loaded + const hasUninitializedChildren = expandedNode.children?.[0]?.key === UNINITIALIZED_CHILD_KEY; + + if (expandedNode.children && !hasUninitializedChildren) { + return; + } + + await loadCostDriverChildren(expandedNode.data.id); + }, + [loadCostDriverChildren] + ); + + /** + * Handles sort field and order updates from the grid. + */ + const handleSortUpdate = useCallback((sortOptions: IDataGridSortEvent): void => { + setSortField(sortOptions.sortField); + setSortOrder(sortOptions.sortOrder); + setHeatMapNameOrder(null); + }, []); + + /** + * Handles heatmap sort — stores the resulting CTF name order and clears the TreeGrid sort + * so the pre-sorted data order from displayedSummaries takes effect. + */ + const handleHeatMapSort = useCallback((sortedNames: string[]): void => { + setHeatMapNameOrder(new Map(sortedNames.map((name, i) => [name, i]))); + setSortField(undefined); + setSortOrder(undefined); + }, []); + + /** + * Handles row toggle (expand/collapse) state updates. + */ + const handleRowToggle = useCallback((keys: string[]): void => { + setExpandedRowKeys(keys); + }, []); + + /** + * Handles heatmap cell click events. + * Navigates to and expands the corresponding row in the TreeGrid, then scrolls to it. + */ + const handleHeatMapCellClick = useCallback( + async (rowName: string, costDriver: string): Promise => { + if (!encounterGroupSummaries) { + return; + } + + // Ignore column header clicks (have cost driver but no row name) + if (costDriver.length > 0 && rowName.length === 0) { + return; + } + + // Find matching row + const matchingRow = encounterGroupSummaries.find((row) => row.data.name === rowName); + if (!matchingRow) { + return; + } + + const rowKey = matchingRow.key; + + // Expand row if not already expanded + if (!expandedRowKeys.includes(rowKey)) { + // Load children if needed + const hasUninitializedChildren = matchingRow.children?.[0]?.key === UNINITIALIZED_CHILD_KEY; + if (!matchingRow.children || hasUninitializedChildren) { + await loadCostDriverChildren(matchingRow.data.id.toString()); + } + + setExpandedRowKeys((prev) => [...prev, rowKey]); + } + + // Scroll to the row + scrollToRow({ + containerRef: treeGridWrapperRef, + rowKey, + dataArray: encounterGroupSummaries + }); + }, + [encounterGroupSummaries, expandedRowKeys, loadCostDriverChildren, scrollToRow] + ); + + const handleAccordionChange = useCallback( + (key: string | string[]) => { + setActiveAccordionKeys((previousShowAccordion) => { + if (!showLegend) { + // When the legend is hidden, mirror the accordion's key selection directly. + return key; + } + // When the legend is visible, keep item '1' open if it was previously open. + const hasLegendOpen = Array.isArray(previousShowAccordion) ? previousShowAccordion.includes('1') : previousShowAccordion === '1'; + // If item '1' was not previously open, behave like the default accordion behavior. + if (!hasLegendOpen) { + return key; + } + // Item '1' was previously open; ensure it stays open in the new state. + if (Array.isArray(key)) { + return Array.from(new Set(['1', ...key.map(String)])); + } + return ['1', String(key)]; + }); + }, + [showLegend] + ); + + // ======================================================================== + // Callbacks - Layout + // ======================================================================== + + /** + * Measures the available height for the TreeGrid scroll body and updates React state. + * Used for initial mount, banner/filter-bar changes, and post-animation sync. + * Queries .p-treetable-scrollable-body directly so Card + column header heights are + * accounted for without hard-coded offsets. + */ + const measureScrollHeight = useCallback(() => { + if (!containerRef.current || !treeGridWrapperRef.current) return; + const scrollBody = treeGridWrapperRef.current.querySelector('.p-treetable-scrollable-body') as HTMLElement | null; + if (!scrollBody) return; + const containerBottom = containerRef.current.getBoundingClientRect().bottom; + const paddingBottom = parseFloat(getComputedStyle(containerRef.current).paddingBottom) || 0; + const available = containerBottom - paddingBottom - scrollBody.getBoundingClientRect().top - CARD_BORDER_WIDTH; + setTreeGridScrollHeight(`${Math.max(0, available)}px`); + }, []); + + // #endregion + + // Derived early so layout effects can reference it as a stable dependency. + const shouldShowHeatmap = useMemo(() => benchmarking && peerGroupFilters?.peerGroupId !== 0, [benchmarking, peerGroupFilters?.peerGroupId]); + + // #region Effects + + // ======================================================================== + // Effects + // ======================================================================== + + /** + * Fetches data when filters or peer group configuration changes. + * Fetches grid data and heatmap data in parallel when both are needed. + */ + const peerGroupId = peerGroupFilters?.peerGroupId ?? 0; + + useEffect(() => { + if (!filters || !exclusionCriteria || !peerGroupFilters) { + return; + } + + // Check if this is a peer group change vs other filter changes + const isPeerGroupChange = previousPeerGroupIdRef.current !== peerGroupFilters.peerGroupId; + previousPeerGroupIdRef.current = peerGroupFilters.peerGroupId; + + // Block peer group changes from triggering loads before initial data is loaded + if (isPeerGroupChange && !initialDataLoadedRef.current) { + return; + } + + // When peer group changes or filters change, clear heatmap data to ensure fresh data fetch + if (isPeerGroupChange || hasAppliedFiltersRef.current) { + setHeatMapData(undefined); + } + + // Handle peer group change by removing SPH criteria if switching to no peer group + if (!firstLoading) { + handlePeerGroupChange(); + } + + // Skip data fetch if filters haven't been applied yet + // UNLESS the peer group changed (then always fetch with the new peer group) + if (!hasAppliedFiltersRef.current && !isPeerGroupChange) { + // Clear firstLoading to prevent page loader from blocking UI + if (firstLoading) { + setFirstLoading(false); + } + return; + } + + // Check if data should be fetched + // Skip this check if peer group changed (peer group is sufficient reason to refetch) + const shouldFetch = shouldFetchData(filters, firstLoading); + if (!isPeerGroupChange && !shouldFetch) { + // Clear firstLoading even if we don't fetch to prevent infinite loading state + // Use ref to prevent triggering a second fetch when firstLoading becomes false + if (firstLoading && !hasHandledFirstLoadRef.current) { + hasHandledFirstLoadRef.current = true; + setFirstLoading(false); + } + return; + } + + fetchData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + filters, + exclusionCriteria?.losExclusionType, + exclusionCriteria?.losExclusionValue, + exclusionCriteria?.costExclusionType, + exclusionCriteria?.costExclusionValue, + peerGroupFilters?.peerGroupId, + benchmarking, + granularity, + markDataAsLoaded + ]); + + /** + * Synchronizes column layout visibility with parent component. + */ + useEffect(() => { + setManageColumnsVisible(setColumnLayoutVisible); + }, [setColumnLayoutVisible, setManageColumnsVisible]); + + /** + * When a Top N filter is applied, automatically sort the grid by the selected Top N field descending. + * This is client-side only — sort state is local to this component and is not persisted or sent to the API. + */ + useEffect(() => { + if (exclusionCriteria?.topN && exclusionCriteria?.topNField) { + setSortField(getTopNSortField(exclusionCriteria.topNField)); + setSortOrder(-1); + setHeatMapNameOrder(null); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [exclusionCriteria?.topN, exclusionCriteria?.topNField]); + + /** + * When returning from detail page, check if filters exist in session storage and reload with them + * This allows users to return to the same state they left off in after drilling into details and back out + */ + useEffect(() => { + // Only reload if filters exist AND we're not in initial loading state + // (which means filters came from session storage after a previous session, not just loaded from config) + if (filters && exclusionCriteria && peerGroupFilters && configuration && !reloadHandledRef.current && !firstLoading) { + // Check if we have meaningful filters (more than just date filter) + const hasNonDateFilters = filters.filterChipItems && filters.filterChipItems.some((chip) => chip.key !== ChipKey.DischargeDate); + + if (hasNonDateFilters) { + reloadHandledRef.current = true; + hasAppliedFiltersRef.current = true; + + fetchData(); + } + } + }, [filters, exclusionCriteria, peerGroupFilters, configuration, benchmarking, firstLoading, fetchData]); + + /** + * Sets container height to fill the viewport and schedules a scroll-height re-measurement. + * Runs after every render (no deps) so banner appearance and filter-bar height changes + * are automatically handled without needing explicit dependencies. + */ + useLayoutEffect(() => { + if (!containerRef.current) return; + const top = containerRef.current.getBoundingClientRect().top; + containerRef.current.style.height = `${window.innerHeight - top - CONTAINER_BOTTOM_GAP}px`; + if (rafIdRef.current !== null) cancelAnimationFrame(rafIdRef.current); + rafIdRef.current = requestAnimationFrame(measureScrollHeight); + return () => { + if (rafIdRef.current !== null) { + cancelAnimationFrame(rafIdRef.current); + rafIdRef.current = null; + } + }; + }); + + /** + * Fires the instant the accordion's height transition ends. + * One measurement, zero timers, zero per-frame work. + * Filtered to height transitions only so unrelated child transitions are ignored. + */ + const handleAccordionTransitionEnd = useCallback( + (e: React.TransitionEvent) => { + if (e.propertyName === 'height') measureScrollHeight(); + }, + [measureScrollHeight] + ); + + /** + * Re-measures container height and scroll height on window resize. + */ + useEffect(() => { + const handleResize = () => { + if (!containerRef.current) return; + const top = containerRef.current.getBoundingClientRect().top; + containerRef.current.style.height = `${window.innerHeight - top - CONTAINER_BOTTOM_GAP}px`; + measureScrollHeight(); + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [measureScrollHeight]); + + // #endregion + + // #region Memoized Values + + // ======================================================================== + // Memoized values + // ======================================================================== + + const columnConfigs = useMemo(() => getColumnsWithVisibility(), [getColumnsWithVisibility]); + + const { columns, headerGroup } = useMemo(() => buildTreeGrid(columnConfigs, { isDetailPage: false }), [columnConfigs]); + + // Summaries after topN filtering only — stable across sort events. + // Used by displayedHeatMapData so heatmap filtering doesn't re-run on every sort. + const filteredSummaries = useMemo(() => { + if (!encounterGroupSummaries) return encounterGroupSummaries; + if (exclusionCriteria?.topN && exclusionCriteria?.topNField) { + return applyTopNFilter(encounterGroupSummaries, exclusionCriteria.topN, exclusionCriteria.topNField); + } + return encounterGroupSummaries; + }, [encounterGroupSummaries, exclusionCriteria?.topN, exclusionCriteria?.topNField]); + + // Summaries after topN filtering and sort ordering applied. + const displayedSummaries = useMemo(() => { + if (!filteredSummaries) return filteredSummaries; + + if (heatMapNameOrder) { + // Heatmap is driving sort — reorder to match CTF name order; unknowns go to the end. + const maxIndex = heatMapNameOrder.size; + return [...filteredSummaries].sort((a, b) => (heatMapNameOrder.get(a.data.name) ?? maxIndex) - (heatMapNameOrder.get(b.data.name) ?? maxIndex)); + } + + if (sortField && sortOrder) { + // Summary grid is driving sort — pre-sort here so treeGridNameOrder can read the order directly. + // Nulls sort to the bottom, consistent with heatmap sort behavior. + return [...filteredSummaries].sort((a, b) => { + const aVal = (a.data as Record)[sortField] as number | string | undefined; + const bVal = (b.data as Record)[sortField] as number | string | undefined; + if (aVal == null && bVal != null) return 1; + if (aVal != null && bVal == null) return -1; + if (aVal == null || bVal == null) return 0; + return (aVal < bVal ? -1 : aVal > bVal ? 1 : 0) * sortOrder; + }); + } + + return filteredSummaries; + }, [filteredSummaries, heatMapNameOrder, sortField, sortOrder]); + + // Heatmap rows filtered to the CTFs present in the grid after topN. + // Depends on filteredSummaries (not displayedSummaries) so it stays stable across sort events. + const displayedHeatMapData = useMemo(() => { + if (!heatMapData || !filteredSummaries) return heatMapData; + const summaryNames = new Set(filteredSummaries.map((s) => s.data.name)); + + // Preserve array reference when all heatmap rows are in the summary set. + // This prevents unnecessary downstream sort-state resets in the heatmap grid. + const isSubset = heatMapData.every((h) => summaryNames.has(h.name)); + if (isSubset) { + return heatMapData; + } + + return heatMapData.filter((h) => summaryNames.has(h.name)); + }, [heatMapData, filteredSummaries]); + + // Name order derived from the TreeGrid's sort, passed to the heatmap so it can mirror the order. + // Null when the heatmap is driving the sort. + // displayedSummaries is already pre-sorted by sortField/sortOrder, so no re-sort needed here. + const treeGridNameOrder = useMemo(() => { + if (!displayedSummaries || !sortField || !sortOrder) return null; + return new Map(displayedSummaries.map((s, i) => [s.data.name, i])); + }, [displayedSummaries, sortField, sortOrder]); + + const emptyMessage = useMemo( + () => + encounterGroupSummaries && encounterGroupSummaries.length === 0 ? ( + 'No Items' + ) : ( + + Select Refinement Options, update the criteria, and click Apply to generate results. + + ), + [encounterGroupSummaries] + ); + + /** + * TreeGrid component displaying case type family summaries with expandable cost driver details. + * Configured with sorting, loading state, and empty message. + * Wrapped in a Card and connected to the treeGridRef for scroll management. + */ + const treeGridComponent = useMemo( + () => ( +
+ {shouldShowHeatmap && ( + // flex: '0 0 auto' — accordion takes its natural height, never grows or shrinks. + // No overflow:hidden here — the legend popover renders in a portal and must not be clipped. +
+ e.stopPropagation()}> + {isCodeLevelEnhancementEnabled && ( + setGranularity(Number(e.target.value) as BenchmarkingInsightsGranularity)} + options={[ + { value: BenchmarkingInsightsGranularity.CaseTypeFamily, label: 'Case Type Family' }, + { value: BenchmarkingInsightsGranularity.DrgCode, label: 'MS-DRG / APR-DRG' }, + { value: BenchmarkingInsightsGranularity.PrimaryCpt, label: 'Primary CPT' } + ]} + /> + )} + +
+ ), + children: ( + + + + ) + } + ]} + /> +
+ )} + {/* + flex: 1 absorbs all remaining vertical space after the accordion. + minHeight: 0 is required — without it a flex child cannot shrink below its content size. + */} +
+ + + {columns} + + +
+ + ), + [ + loading, + sortField, + sortOrder, + displayedSummaries, + expandedRowKeys, + headerGroup, + emptyMessage, + columns, + gridResetKey, + handleNodeExpand, + handleSortUpdate, + handleRowToggle, + shouldShowHeatmap, + activeAccordionKeys, + handleAccordionChange, + showLegend, + displayedHeatMapData, + heatMapLoading, + handleHeatMapCellClick, + handleHeatMapSort, + treeGridNameOrder, + isCodeLevelEnhancementEnabled, + granularity, + setGranularity, + treeGridScrollHeight, + handleAccordionTransitionEnd + ] + ); + + // #endregion + + // #region Render + + // ======================================================================== + // Render + // ======================================================================== + + return ( + <> + {/* Benchmarking Banner */} + + + {/* Filter Bar */} + {renderFilterBar()} + + {/* Main Content: Heatmap and TreeGrid */} + {treeGridComponent} + + {/* Column Manager Dialog */} + {renderColumnManagerDialog({ + isVisible: manageColumnsVisible, + onCancel: onHideColumnLayout, + onApplyComplete: onHideColumnLayout + })} + + ); +}; + +// #endregion + +// #endregion + +export default CaseTypeFamilySummaryGrid; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/Exploration.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/Exploration.tsx new file mode 100644 index 0000000..88d7ceb --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/Exploration.tsx @@ -0,0 +1,31 @@ +import { useHelpCenter } from '@strata/core/lib'; +import Button from '@strata/tempo/lib/button'; +import Header from '@strata/tempo/lib/header'; +import InfoCircleIcon from '@strata/tempo/lib/icon/InfoCircleIcon'; +import Spacing from '@strata/tempo/lib/spacing/Spacing'; +import React from 'react'; +import CaseTypeFamilySummaryGrid from './CaseTypeFamilySummaryGrid'; + +const Encounter: React.FC = () => { + const helpCenter = useHelpCenter(); + const [manageColumnsVisible, setManageColumnsVisible] = React.useState(false); + + return ( + <> +
+ + }> + {`This population is read-only because ${associatedInitiativesCount} ${ + associatedInitiativesCount > 1 ? 'initiatives' : 'initiative' + } associated with it. Duplicate the population to make changes.`} + + )} +
+ + + + ) + } + type='info' + > + Only certain criteria apply to benchmarking data. See Help Center to learn more. + + ); +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/BenchmarkingGridHeatMap.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/BenchmarkingGridHeatMap.tsx new file mode 100644 index 0000000..ffdb17d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/BenchmarkingGridHeatMap.tsx @@ -0,0 +1,218 @@ +import { formatNumber } from '@strata/intl/lib'; +import DataGrid, { IDataGridSortEvent } from '@strata/tempo/lib/datagrid'; +import Text from '@strata/tempo/lib/text'; +import Tooltip from '@strata/tempo/lib/tooltip'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { IHeatMapData, IHeatMapPivotedRow, pivotHeatMapData } from '../data/IHeatMapData'; +import { getHeatMapBorderStyle, getHeatMapColor, getHeatMapColorDescription } from '../data/heatMapColors'; + +// Nulls sort to the bottom, consistent with summary grid sort behavior. +function sortRows(rowsToSort: IHeatMapPivotedRow[], field: string, order: number): IHeatMapPivotedRow[] { + return [...rowsToSort].sort((a, b) => { + const aVal = a[field] as number | string | null | undefined; + const bVal = b[field] as number | string | null | undefined; + if (aVal == null && bVal != null) return 1; + if (aVal != null && bVal == null) return -1; + if (aVal == null || bVal == null) return 0; + return (aVal < bVal ? -1 : aVal > bVal ? 1 : 0) * order; + }); +} + +interface IBenchmarkingHeatMapGridProps { + /** Raw heatmap data from API */ + heatMapData?: IHeatMapData[]; + /** Loading state */ + loading?: boolean; + /** Callback when a cell is clicked - provides row name and cost driver */ + onCellClick?: (rowName: string, costDriver: string) => void; + /** Callback when the grid is sorted — provides the resulting CTF name order */ + onSort?: (sortedNames: string[]) => void; + /** External name order from the TreeGrid sort — heatmap mirrors this order when provided */ + nameOrder?: Map | null; +} + +/** + * Renders an O/E ratio cell with heatmap coloring. + * Uses Tempo-approved color palette with opacity variants. + */ +const OeRatioCell: React.FC<{ + oeRatio: number | null; + rowName: string; + costDriver: string; + clickedDivRef: React.MutableRefObject; + onCellClick?: (rowName: string, costDriver: string) => void; +}> = ({ oeRatio, rowName, costDriver, clickedDivRef, onCellClick }) => { + const backgroundColor = getHeatMapColor(oeRatio); + const description = getHeatMapColorDescription(oeRatio); + const displayValue = oeRatio !== null ? formatNumber(oeRatio, 'decimal') : '-'; + + const handleClick = (event: React.MouseEvent) => { + if (!onCellClick) { + return; + } + + if (clickedDivRef.current) { + clickedDivRef.current.style.border = getHeatMapBorderStyle(); // Reset default border + } + clickedDivRef.current = event.currentTarget; + event.currentTarget.style.border = getHeatMapBorderStyle(event.currentTarget.textContent || ''); + onCellClick(rowName, costDriver); + }; + + return ( + +
+ {displayValue} +
+
+ ); +}; + +/** + * Benchmarking Heatmap Grid component. + * + * Displays a pivoted grid where: + * - Rows are case type families (or codes when includeCodeLevel is true) + * - Columns are cost drivers (Pharmacy, Laboratory, etc.) + * - Cell values are O/E ratios with color-coded backgrounds + * + * Color Legend (Tempo-approved palette): + * - Red shades: O/E > 1 (customer costs exceed benchmark - unfavorable) + * - Green shades: O/E < 1 (customer costs below benchmark - favorable) + * - Transparent: O/E = 1 or no data + */ +export const BenchmarkingHeatMapGrid: React.FC = (props: IBenchmarkingHeatMapGridProps) => { + const { heatMapData, loading, onCellClick, onSort: onSortProp, nameOrder } = props; + const clickedDivRef = React.useRef(null); + + // Controlled sort state — reset when data changes or TreeGrid takes over sorting + const [sortField, setSortField] = useState(undefined); + const [sortOrder, setSortOrder] = useState(undefined); + + // Pivot the data: rows are case type families, columns are cost drivers + const { rows: pivotedRows, costDrivers } = useMemo(() => { + if (!heatMapData || heatMapData.length === 0) { + return { rows: [], costDrivers: [] }; + } + return pivotHeatMapData(heatMapData); + }, [heatMapData]); + + // When nameOrder is provided (TreeGrid sorted), sort rows to match. + // When null (heatmap is driving sort), apply local sort or use original order. + const rows = useMemo(() => { + if (nameOrder) { + const maxIndex = nameOrder.size; + return [...pivotedRows].sort((a, b) => (nameOrder.get(a.name) ?? maxIndex) - (nameOrder.get(b.name) ?? maxIndex)); + } + + if (sortField && sortOrder) { + return sortRows(pivotedRows, sortField, sortOrder); + } + + return pivotedRows; + }, [pivotedRows, nameOrder, sortField, sortOrder]); + + const handleSort = useCallback( + (e: IDataGridSortEvent) => { + setSortField(e.sortField); + setSortOrder(e.sortOrder); + + if (onSortProp) { + onSortProp(sortRows(pivotedRows, e.sortField, e.sortOrder).map((r) => r.name)); + } + }, + [onSortProp, pivotedRows] + ); + + // Reset clicked cell reference and sort state when data changes + useEffect(() => { + if (clickedDivRef.current) { + clickedDivRef.current.style.border = ''; + } + clickedDivRef.current = null; + setSortField(undefined); + setSortOrder(undefined); + }, [heatMapData]); + + // When TreeGrid sort takes over (nameOrder provided), clear local sort + useEffect(() => { + if (nameOrder) { + setSortField(undefined); + setSortOrder(undefined); + } + }, [nameOrder]); + + // Build columns array for DataGrid + const columns = useMemo(() => { + const cols: React.ReactElement[] = []; + + // Name column (Case Type Family or Code) + cols.push( + ( +
onCellClick && onCellClick(rowData.name, '')} + onKeyDown={(event) => { + if (!onCellClick) { + return; + } + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + onCellClick(rowData.name, ''); + } + }} + role='button' + tabIndex={0} + style={{ cursor: onCellClick ? 'pointer' : 'default' }} + > + {rowData.name} +
+ )} + /> + ); + + // Dynamic cost driver columns + costDrivers.forEach((costDriver) => { + cols.push( + ( + + )} + /> + ); + }); + + cols.push(); + return cols; + }, [costDrivers, onCellClick]); + + return ( + + {columns} + + ); +}; + +export default BenchmarkingHeatMapGrid; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/BenchmarkingLegendView.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/BenchmarkingLegendView.tsx new file mode 100644 index 0000000..d1d86fd --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/BenchmarkingLegendView.tsx @@ -0,0 +1,173 @@ +// #region Imports + +// Third-party imports +import React from 'react'; + +// Strata Tempo UI imports +import Button from '@strata/tempo/lib/button/Button'; +import Popover from '@strata/tempo/lib/popover/Popover'; +import Spacing from '@strata/tempo/lib/spacing'; +import Text from '@strata/tempo/lib/text/Text'; + +// Internal imports - data & utilities +import { getHeatMapColor, HEATMAP_COLORS, OE_RATIO_THRESHOLDS } from '../data/heatMapColors'; + +// #endregion + +// #region Constants + +// ============================================================================ +// Constants +// ============================================================================ + +/** + * Legend content for the heatmap, mapping OE ratio thresholds to their display labels. + * Used to display the comparison factor legend in the heatmap accordion. + */ +const formatOeRatioThreshold = (value: number): string => value.toFixed(2); + +const createLegendContent = (): { oeRatio: number; label: string }[] => { + const thresholds = OE_RATIO_THRESHOLDS; + + const highRed = thresholds.HIGH_RED; + const mediumRed = thresholds.MEDIUM_RED; + const lowRed = thresholds.LOW_RED; + const neutral = thresholds.NEUTRAL; + const lowGreen = thresholds.LOW_GREEN; + const mediumGreen = thresholds.MEDIUM_GREEN; + const highGreen = thresholds.HIGH_GREEN; + + return [ + { + oeRatio: highRed, + label: `>= ${formatOeRatioThreshold(highRed)}` + }, + { + oeRatio: mediumRed, + label: `>= ${formatOeRatioThreshold(mediumRed)} and < ${formatOeRatioThreshold(highRed)}` + }, + { + oeRatio: lowRed, + label: `>= ${formatOeRatioThreshold(lowRed)} and < ${formatOeRatioThreshold(mediumRed)}` + }, + { + oeRatio: neutral, + label: `= ${formatOeRatioThreshold(neutral)}` + }, + { + oeRatio: lowGreen, + label: `>= ${formatOeRatioThreshold(mediumGreen)} and < ${formatOeRatioThreshold(neutral)}` + }, + { + oeRatio: mediumGreen, + label: `>= ${formatOeRatioThreshold(highGreen)} and < ${formatOeRatioThreshold(mediumGreen)}` + }, + { + oeRatio: highGreen, + label: `< ${formatOeRatioThreshold(highGreen)}` + } + ]; +}; + +export const LEGEND_CONTENT = createLegendContent(); +// #endregion + +// #region Types + +// ============================================================================ +// Types +// ============================================================================ + +/** + * Props for the HeatMapLegendSwatch component. + */ +export type HeatMapLegendSwatchProps = { + /** The background color for the legend swatch, determined by the OE ratio thresholds */ + color: string; + /** The border style for the legend swatch, used to indicate the threshold boundary */ + border: string; + /** The label describing the OE ratio range for this legend item */ + label: string; +}; + +/** + * Props for the BenchmarkingLegendPopover component. + */ +export type BenchmarkingLegendPopoverProps = { + /** Whether the legend popover is currently open */ + open: boolean; + /** Callback fired when the popover open state changes */ + onOpenChange: (open: boolean) => void; +}; + +// #endregion + +// #region Components + +// ============================================================================ +// Components +// ============================================================================ + +/** + * HeatMapLegendSwatch displays a single item in the heatmap legend. + * Shows a colored box with a corresponding OE ratio label. + * + * @param color - The background color for the legend swatch, determined by the OE ratio thresholds. + * @param border - The border style for the legend swatch, used to indicate the threshold boundary. + * @param label - The label describing the OE ratio range for this legend item. + * @returns A React element representing a single item in the heatmap legend, consisting of a colored box and a label. + */ +export const HeatMapLegendSwatch: React.FC = ({ color, border, label }) => ( + +
+   +
+ {label} +
+); + +HeatMapLegendSwatch.displayName = 'HeatMapLegendSwatch'; + +/** + * BenchmarkingLegendPopover displays the heatmap legend in a popover. + * Shows colored swatches with OE ratio labels and provides a button to open the popover. + * + * @param open - Whether the legend popover is currently open + * @param onOpenChange - Callback fired when the popover open state changes + * @returns A popover component with legend content and a "View Legend" button + */ +export const BenchmarkingLegendPopover: React.FC = ({ open, onOpenChange }) => ( + + {LEGEND_CONTENT.map(({ oeRatio, label }) => ( + + ))} + + } + > + + +); + +BenchmarkingLegendPopover.displayName = 'BenchmarkingLegendPopover'; + +// #endregion diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/BuildTreeGrid.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/BuildTreeGrid.tsx new file mode 100644 index 0000000..c26f25f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/BuildTreeGrid.tsx @@ -0,0 +1,303 @@ +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +import React, { JSX } from 'react'; +import DataHeaderCell from '@strata/tempo/lib/dataheader/DataHeaderCell'; +import DataHeaderGroup from '@strata/tempo/lib/dataheader/DataHeaderGroup'; +import DataHeaderRow from '@strata/tempo/lib/dataheader/DataHeaderRow'; +import Text from '@strata/tempo/lib/text'; +import TreeGrid from '@strata/tempo/lib/treegrid'; +import { ColumnGroup, isGapColumn, isNumberColumn, ITreeGridColumnConfig, DEFAULT_GRID_PX_WIDTH } from '../data/ITreeGridColumnConfig'; + +// #region Type Definitions + +/** + * Configuration options for building TreeGrid components. + */ +export interface IBuildTreeGridOptions { + /** Whether the grid is displayed on the detail page (affects detail column visibility). */ + isDetailPage: boolean; +} + +/** + * Result containing both TreeGrid columns and header group. + */ +export interface IBuildTreeGridResult { + /** Array of TreeGrid column elements ready to render. */ + columns: JSX.Element[]; + /** DataHeaderGroup element for TreeGrid header. */ + headerGroup: JSX.Element; +} + +// #endregion + +// #region Constants + +/** + * Header group display configuration mapping column groups to their display names. + * Groups with null values (name, detail) do not display group headers. + */ +const GROUP_HEADERS: Record = { + name: null, + detail: null, + entirePopulation: 'Entire Population', + refinedPopulation: 'Refined Population', + benchmark: 'Benchmark Data', + excludedPopulation: 'Excluded Population' +}; + +// #endregion + +// #region Helper Functions + +/** + * Gets visible (non-hidden, non-gap) columns for a specific group. + * @param configs - Array of column configurations + * @param group - The column group to filter by + * @returns Array of visible columns in the group + */ +const getVisibleColumnsForGroup = (configs: ITreeGridColumnConfig[], group: ColumnGroup): ITreeGridColumnConfig[] => { + return configs.filter((c) => c.group === group && !c.hidden && !c.isGap); +}; + +/** + * Checks if a group has any visible columns. + * @param configs - Array of column configurations + * @param group - The column group to check + * @returns True if the group has at least one visible column + */ +const hasVisibleColumns = (configs: ITreeGridColumnConfig[], group: ColumnGroup): boolean => { + return getVisibleColumnsForGroup(configs, group).length > 0; +}; + +/** + * Calculates the number of visible columns in a group (for colspan). + * @param configs - Array of column configurations + * @param group - The column group to calculate span for + * @returns Number of visible columns in the group + */ +const getGroupSpan = (configs: ITreeGridColumnConfig[], group: ColumnGroup): number => { + return getVisibleColumnsForGroup(configs, group).length; +}; + +/** + * Calculates the total width for all visible columns in a group. + * @param configs - Array of column configurations + * @param group - The column group to calculate width for + * @returns Total width in pixels + */ +const getGroupWidth = (configs: ITreeGridColumnConfig[], group: ColumnGroup): number => { + return getVisibleColumnsForGroup(configs, group).reduce((sum, col) => sum + (col.width ?? DEFAULT_GRID_PX_WIDTH), 0); +}; + +/** + * Checks if a gap column exists after the specified group. + * @param configs - Array of column configurations + * @param group - The column group to check for gaps after + * @returns True if a gap exists after the group + */ +const hasGapAfterGroup = (configs: ITreeGridColumnConfig[], group: ColumnGroup): boolean => { + return configs.some((c) => c.group === group && c.isGap); +}; + +/** + * Gets unique column groups in the order they appear in configurations. + * @param configs - Array of column configurations + * @returns Array of unique groups in display order + */ +const getGroupOrder = (configs: ITreeGridColumnConfig[]): ColumnGroup[] => { + const groupOrder: ColumnGroup[] = []; + const seenGroups = new Set(); + + configs.forEach((config) => { + if (!seenGroups.has(config.group) && !config.isGap) { + groupOrder.push(config.group); + seenGroups.add(config.group); + } + }); + + return groupOrder; +}; + +// #endregion + +// #region Column Builders + +/** + * Builds TreeGrid column elements from column configurations. + * @param configs - Array of column configurations (excluding hidden columns) + * @returns Array of TreeGrid column JSX elements + */ +const buildTreeGridColumns = (configs: ITreeGridColumnConfig[]): JSX.Element[] => { + return configs.map((config, index) => { + const key = config.key ?? config.field ?? `col-${index}`; + + if (isGapColumn(config)) { + return ; + } + + if (isNumberColumn(config)) { + return ( + + ); + } + + return ( + + ); + }); +}; + +// #endregion + +// #region Header Builders + +/** + * Builds the primary header row containing group headers. + * @param configs - Array of column configurations + * @param isDetailPage - Whether the grid is on the detail page + * @returns DataHeaderRow element with group headers + */ +const buildPrimaryHeaderRow = (configs: ITreeGridColumnConfig[], isDetailPage: boolean): JSX.Element => { + const headerCells: JSX.Element[] = []; + const groupOrder = getGroupOrder(configs); + + groupOrder.forEach((group) => { + if (!hasVisibleColumns(configs, group)) { + return; + } + + // Special handling for name and detail columns (no group headers) + if (group === 'name' || group === 'detail') { + const width = getGroupWidth(configs, group); + const key = group === 'name' ? 'name-header' : 'detail-header'; + + // Only show detail column on detail page + if (group === 'detail' && !isDetailPage) { + return; + } + + headerCells.push(); + + // Add gap after name column (column 2 position) + if (group === 'name') { + headerCells.push(); + } + + return; + } + + // Regular group headers + const span = getGroupSpan(configs, group); + const width = getGroupWidth(configs, group); + const headerText = GROUP_HEADERS[group]; + + if (headerText) { + headerCells.push({headerText}} align='center' />); + } + + // Add gap after group if it exists + if (hasGapAfterGroup(configs, group)) { + headerCells.push(); + } + }); + + return {headerCells}; +}; + +/** + * Builds the secondary header row containing individual column headers. + * @param configs - Array of column configurations (only visible columns) + * @returns DataHeaderRow element with column headers + */ +const buildSecondaryHeaderRow = (configs: ITreeGridColumnConfig[]): JSX.Element => { + const headerCells: JSX.Element[] = []; + + // Only include visible, non-gap columns + const visibleColumns = configs.filter((c) => !c.hidden && !c.isGap); + + visibleColumns.forEach((col, index) => { + headerCells.push( + + ); + }); + + return {headerCells}; +}; + +/** + * Builds the complete header group with primary and secondary rows. + * @param configs - Array of column configurations + * @param isDetailPage - Whether the grid is on the detail page + * @returns DataHeaderGroup element + */ +const buildHeaderGroup = (configs: ITreeGridColumnConfig[], isDetailPage: boolean): JSX.Element => { + return ( + + {buildPrimaryHeaderRow(configs, isDetailPage)} + {buildSecondaryHeaderRow(configs)} + + ); +}; + +// #endregion + +// #region Main Export + +/** + * Builds both TreeGrid columns and header group from unified column configurations. + * Ensures columns and headers stay synchronized by deriving both from the same source. + * + * @param configs - Array of column configurations with visibility and group settings + * @param options - Build options including detail page flag + * @returns Object containing both TreeGrid columns and header group elements + * + * @example + * const { columns, headerGroup } = buildTreeGrid(columnConfigs, { isDetailPage: true }); + * + * + * {columns} + * + */ +export const buildTreeGrid = (configs: ITreeGridColumnConfig[], options: IBuildTreeGridOptions): IBuildTreeGridResult => { + const { isDetailPage } = options; + + // Filter to only visible columns for rendering + const visibleConfigs = configs.filter((config) => !config.hidden); + + // Build column elements + const columns = buildTreeGridColumns(visibleConfigs); + + // Build header group + const headerGroup = buildHeaderGroup(configs, isDetailPage); + + return { columns, headerGroup }; +}; + +// #endregion diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/Filters.test.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/Filters.test.tsx new file mode 100644 index 0000000..6791164 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/Filters.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { render } from '../../test-utils/testUtils'; +import { dropDownTestUtils } from '@strata/test-utils/lib'; +import Filters from './Filters'; +import { newExclusionCriteria, parseLosTrimValue, parseCostTrimValue } from '../data/IExclusionCriteria'; + +const baseProps = { + handleALOSChange: jest.fn(), + handleTDVCChange: jest.fn(), + parseLosTrimValue, + parseCostTrimValue, + exclusionCriteria: newExclusionCriteria() +}; + +test('Length of Stay dropdown defaults to No Exclusion', () => { + render(); + const losDropdown = dropDownTestUtils.getDropDown('Length of Stay'); + expect(dropDownTestUtils.getSelectedOption(losDropdown)).toEqual('No Exclusion'); +}); + +test('Variable Direct Cost dropdown defaults to No Exclusion', () => { + render(); + const vdcDropdown = dropDownTestUtils.getDropDown('Variable Direct Cost'); + expect(dropDownTestUtils.getSelectedOption(vdcDropdown)).toEqual('No Exclusion'); +}); + +test('handleALOSChange is called when LOS selection changes', async () => { + const handleALOSChange = jest.fn(); + render(); + const losDropdown = dropDownTestUtils.getDropDown('Length of Stay'); + await dropDownTestUtils.selectOption(losDropdown, 'Exclude > 2 Deviations'); + expect(handleALOSChange).toHaveBeenCalled(); +}); + +test('handleTDVCChange is called when VDC selection changes', async () => { + const handleTDVCChange = jest.fn(); + render(); + const vdcDropdown = dropDownTestUtils.getDropDown('Variable Direct Cost'); + await dropDownTestUtils.selectOption(vdcDropdown, 'Exclude > 2 Deviations'); + expect(handleTDVCChange).toHaveBeenCalled(); +}); diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/Filters.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/Filters.tsx new file mode 100644 index 0000000..d61bed6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/Filters.tsx @@ -0,0 +1,101 @@ +import DropDown, { DropDownValue } from '@strata/tempo/lib/dropdown/DropDown'; +import Spacing from '@strata/tempo/lib/spacing/Spacing'; +import Text from '@strata/tempo/lib/text'; +import React from 'react'; +import IExclusionCriteria from '../data/IExclusionCriteria'; + +export interface IFiltersProps { + handleALOSChange: (value: DropDownValue) => void; + handleTDVCChange: (value: DropDownValue) => void; + parseLosTrimValue: (exclusionCriteria: IExclusionCriteria) => void; + parseCostTrimValue: (exclusionCriteria: IExclusionCriteria) => void; + exclusionCriteria: IExclusionCriteria; +} + +const Filters: React.FC = (props) => { + const { handleALOSChange, handleTDVCChange, parseLosTrimValue, parseCostTrimValue, exclusionCriteria } = props; + + const handleParseLosTrimValue = (exclusionCriteria: IExclusionCriteria) => { + if (parseLosTrimValue) { + return parseLosTrimValue(exclusionCriteria); + } + return 'none0'; + }; + const handleParseCostTrimValue = (exclusionCriteria: IExclusionCriteria) => { + if (parseCostTrimValue) { + return parseCostTrimValue(exclusionCriteria); + } + return 'none0'; + }; + + return ( + <> + + Length of Stay + + + No Exclusion + + + Median Absolute Deviation (MAD) + + + Exclude > 2 Deviations + + + Exclude > 3 Deviations + + + Interquartile Range (IQR) + + + Exclude > 1 IQR + + + Exclude > 1.5 IQR + + + Exclude > 2 IQR + + + Exclude > 2.5 IQR + + + + + Variable Direct Cost + + + No Exclusion + + + Median Absolute Deviation (MAD) + + + Exclude > 2 Deviations + + + Exclude > 3 Deviations + + + Interquartile Range (IQR) + + + Exclude > 1 IQR + + + Exclude > 1.5 IQR + + + Exclude > 2 IQR + + + Exclude > 2.5 IQR + + + + + ); +}; + +export default Filters; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/RefinementOptionsDrawer.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/RefinementOptionsDrawer.tsx new file mode 100644 index 0000000..2d370d5 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/RefinementOptionsDrawer.tsx @@ -0,0 +1,278 @@ +import { useHelpCenter } from '@strata/core/lib/help/useHelpCenter'; +import Button from '@strata/tempo/lib/button/Button'; +import Drawer from '@strata/tempo/lib/drawer/Drawer'; +import DropDown from '@strata/tempo/lib/dropdown/DropDown'; +import InfoCircleIcon from '@strata/tempo/lib/icon/InfoCircleIcon'; +import PlusIcon from '@strata/tempo/lib/icon/PlusIcon'; +import InputNumber from '@strata/tempo/lib/inputnumber/InputNumber'; +import useModal from '@strata/tempo/lib/modal/useModal'; +import Popover from '@strata/tempo/lib/popover/Popover'; +import Spacing from '@strata/tempo/lib/spacing/Spacing'; +import Text from '@strata/tempo/lib/text'; +import dayjs from 'dayjs'; +import React from 'react'; +import { ChipKey, ChipType, IFilterChipItem } from '../../shared/components/FilterBar/data/IFilterChipItem'; +import FilterChip from '../../shared/components/FilterBar/FilterChip'; +import FilterTree from '../../shared/components/FilterBar/FilterTree'; +import IExclusionCriteria, { parseCostTrimValue, parseLosTrimValue, TopNField } from '../data/IExclusionCriteria'; +import IFilter from '../data/IFilter'; +import { useBenchmarkingBanner } from '../hooks/useBenchmarkingBanner'; +import { useRefinementDrawer } from '../hooks/useRefinementDrawer'; +import { BenchmarkingBannerText } from './BenchmarkingBannerText'; +import Filters from './Filters'; + +export interface IRefinementOptionsDrawerProps { + isVisible: boolean; + filterId: string; + filterChips: IFilterChipItem[]; + excludedChips: ChipKey[]; + hiddenChips: ChipKey[]; + dateRange: [dayjs.Dayjs, dayjs.Dayjs]; + exclusionCriteria: IExclusionCriteria; + hasBenchmarking?: boolean; + hideTopNFilter?: boolean; + onClose: () => void; + onApply?: (drawerExclusionCriteria: IExclusionCriteria, drawerFilters: IFilter) => void; + setDateRange: (dateRange: [dayjs.Dayjs, dayjs.Dayjs]) => void; + onChangeEncounters?: (resolve: any) => void; +} + +const RefinementOptionsDrawer: React.FC = (props) => { + const { isVisible, filterChips, excludedChips, hiddenChips, dateRange, exclusionCriteria, hasBenchmarking, hideTopNFilter, onApply, onClose, setDateRange, onChangeEncounters } = props; + + const helpCenter = useHelpCenter(); + const modal = useModal(); + + // Manage benchmarking banner visibility + // Note: peerGroupId is omitted here as the drawer doesn't have peer group context + // The banner will show based solely on the hasBenchmarking flag + const { showBanner: showBannerForFilters, onDismiss: onDismissForFilters } = useBenchmarkingBanner({ benchmarking: hasBenchmarking ?? false }); + + // Use hook to manage all drawer state and logic + const { + filters, + drawerChipFilters, + drawerExclusionCriteria, + activeFilterId, + isFilterPopoverVisible, + selectedKeys, + handleOpenChange, + handleClose, + handleSelect, + handleAddFilter, + isSphPopoverVisible, + sphSelectedKeys, + sphCustomItems, + handleSphOpenChange, + handleSphClose, + handleSphSelect, + handleAddSphFilter, + handleChipOpenChange, + handleFilterChange, + handleChipDelete, + handleChipClick, + handleALOSChange, + handleTDVCChange, + handleTopNFieldChange, + handleTopNChange, + getApplyPayload + } = useRefinementDrawer({ + isVisible, + filterChips, + excludedChips, + dateRange, + exclusionCriteria, + hasBenchmarking, + setDateRange + }); + + const handleApplyClick = () => { + const applyChanges = () => { + if (onApply) { + const { drawerExclusionCriteria: criteria, activeFilters } = getApplyPayload(); + onApply(criteria, { filterChipItems: activeFilters }); + } + onClose(); + }; + + if (onChangeEncounters) { + onChangeEncounters(applyChanges); + } else if (!drawerChipFilters?.some((fil) => fil.chipType !== ChipType.dateRange)) { + modal.confirmChanges({ + title: 'Loading data without filters may take a while. To speed up the process, please apply filters to narrow down the results.', + okText: 'Continue', + cancelText: 'Refinement Options', + onOk() { + applyChanges(); + } + }); + } else { + applyChanges(); + } + }; + + return ( + onClose()} + width={500} + footer={ + + + + + + + + } + > + + + + Population Criteria + + + + + + chip.key as ChipKey) + ]} + selectedKeys={selectedKeys} + isVisible={isFilterPopoverVisible} + hasBenchmarking={hasBenchmarking} + treeHeight={310} + onSelect={handleSelect} + /> + + + + + + } + > + + + + chip.chipType !== ChipType.sphCriteria)} + activeFilterId={activeFilterId} + disabledChips={excludedChips} + hiddenChips={hiddenChips} + dateStart={dateRange[0]} + dateEnd={dateRange[1]} + onChipClick={handleChipClick} + onChipDelete={handleChipDelete} + onOpenChange={handleChipOpenChange} + onFilterChange={handleFilterChange} + isReadOnly={false} + /> + + + Outlier Exclusions + + + {Filters({ handleALOSChange, handleTDVCChange, parseLosTrimValue, parseCostTrimValue, exclusionCriteria: drawerExclusionCriteria })} + {/* SPH Criteria Section - Only show when benchmarking is enabled */} + {hasBenchmarking && ( + <> + + + Benchmarking Criteria + + + + + !(drawerChipFilters ?? []).filter((chip) => chip.chipType === ChipType.sphCriteria).some((existingChip) => existingChip.key === sphItem.key) + ) + ]} + onSelect={handleSphSelect} + treeHeight={310} + /> + + + + + + } + > + + + + + chip.chipType === ChipType.sphCriteria)} + activeFilterId={activeFilterId} + disabledChips={excludedChips} + hiddenChips={hiddenChips} + dateStart={dateRange[0]} + dateEnd={dateRange[1]} + onChipClick={handleChipClick} + onChipDelete={handleChipDelete} + onOpenChange={handleChipOpenChange} + onFilterChange={handleFilterChange} + isReadOnly={false} + /> + + )} + {!hideTopNFilter && ( + <> + + + Top N Filter + + + + Dimension + handleTopNFieldChange && handleTopNFieldChange((value as TopNField) ?? null)}> + Encounter Volume + {hasBenchmarking && O/E Variable Direct Cost} + Cost Driver Identified Variation + + Limit (1–50) + handleTopNChange && handleTopNChange(typeof value === 'number' ? Math.max(1, Math.min(value, 50)) : null)} + /> + + + )} + + ); +}; + +export default RefinementOptionsDrawer; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/StrataSignalPopover.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/StrataSignalPopover.tsx new file mode 100644 index 0000000..8934969 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/components/StrataSignalPopover.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import Popover from '@strata/tempo/lib/popover'; +import Spacing from '@strata/tempo/lib/spacing/Spacing'; +import Text from '@strata/tempo/lib/text'; +import { PropsWithChildren } from 'react'; +import CustomIcon from '@strata/tempo/lib/icon/CustomIcon'; + +const StrataSignalIconSvg = () => ( + + + + + + + + + + + + + + + + +); + +const StrataSignalIconWithTextSvg = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +// tempo's CustomIcon goes up to 40px, so for larger sizes we use AntDesign's Icon component +export const StrataSignalIcon = () => ; +export const StrataSignalIconWithText = () => ; + +const StrataSignalPopoverContent = () => { + return ( + + +

You are viewing benchmark results generated using the StrataSphere Cost Model, Strata's standard cost model template used to produce consistent, comparable costing outputs.

+

Your results are shown alongside a benchmark cohort, which groups organizations with similar characteristics to provide context for comparison.

+

Benchmark values reflect cohort-level aggregates and should be interpreted as directional reference points rather than exact matches to your organization’s unique costing approach.

+
+ + + + Powered by + + + + +
+ ); +}; + +// adds a popover around the icon and any children passed in +export const StrataSignalPopover = (props: PropsWithChildren) => { + return ( + +
+ {props.children} {StrataSignalIcon()} +
+
+ ); +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/BenchmarkingInsightsGranularity.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/BenchmarkingInsightsGranularity.ts new file mode 100644 index 0000000..375643b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/BenchmarkingInsightsGranularity.ts @@ -0,0 +1,5 @@ +export enum BenchmarkingInsightsGranularity { + CaseTypeFamily = 0, + DrgCode = 1, + PrimaryCpt = 2 +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/CaseTypeFamilyFunctions.test.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/CaseTypeFamilyFunctions.test.ts new file mode 100644 index 0000000..2dba103 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/CaseTypeFamilyFunctions.test.ts @@ -0,0 +1,90 @@ +import { applyTopNFilter, getTopNSortField } from './CaseTypeFamilyFunctions'; +import { IEncounterGroupTreeGridData } from './IEncounterGroupSummary'; +import { TopNField } from './IExclusionCriteria'; + +const makeSummary = ( + name: string, + totalEncounters: number, + costOeRatio: number | undefined, + identifiedVariation: number +): IEncounterGroupTreeGridData => ({ + key: name, + leaf: true, + children: [], + data: { + key: name, + id: 1, + name, + totalEncounters, + costOeRatio, + identifiedVariation, + totalVariationEncounters: 0, + includedEncounterCount: 0, + mortalityRate: 0, + medianLos: 0, + averageLengthOfStay: 0, + medianCost: 0, + includedEncountersBelowMedian: 0, + includedEncountersAboveMedian: 0, + includedVariation: 0, + excludedEncountersAboveMedian: 0, + excludedVariation: 0, + peerMedianALos: 0, + peerMedianCost: 0, + customerBenchmarkMedianCost: 0, + customerBenchmarkALos: 0 + } +}); + +describe('getTopNSortField', () => { + test('maps EncounterVolume to totalEncounters', () => { + expect(getTopNSortField(TopNField.EncounterVolume)).toBe('totalEncounters'); + }); + + test('maps CostOeRatio to costOeRatio', () => { + expect(getTopNSortField(TopNField.CostOeRatio)).toBe('costOeRatio'); + }); + + test('maps IdentifiedVariation to identifiedVariation', () => { + expect(getTopNSortField(TopNField.IdentifiedVariation)).toBe('identifiedVariation'); + }); +}); + +describe('applyTopNFilter', () => { + test('sorts descending by EncounterVolume and slices to N', () => { + const summaries = [makeSummary('A', 100, 1, 500), makeSummary('B', 300, 1, 200), makeSummary('C', 200, 1, 100)]; + const result = applyTopNFilter(summaries, 2, TopNField.EncounterVolume); + expect(result).toHaveLength(2); + expect(result[0].data.name).toBe('B'); + expect(result[1].data.name).toBe('C'); + }); + + test('sorts descending by CostOeRatio, nulls sort last', () => { + const summaries = [makeSummary('A', 0, undefined, 0), makeSummary('B', 0, 2.5, 0), makeSummary('C', 0, 1.0, 0)]; + const result = applyTopNFilter(summaries, 3, TopNField.CostOeRatio); + expect(result[0].data.name).toBe('B'); + expect(result[1].data.name).toBe('C'); + expect(result[2].data.name).toBe('A'); + }); + + test('sorts descending by IdentifiedVariation and slices to N', () => { + const summaries = [makeSummary('A', 0, 0, 50), makeSummary('B', 0, 0, 200), makeSummary('C', 0, 0, 100)]; + const result = applyTopNFilter(summaries, 2, TopNField.IdentifiedVariation); + expect(result).toHaveLength(2); + expect(result[0].data.name).toBe('B'); + expect(result[1].data.name).toBe('C'); + }); + + test('returns all items when N >= array length', () => { + const summaries = [makeSummary('A', 10, 1, 10), makeSummary('B', 20, 2, 20), makeSummary('C', 30, 3, 30)]; + const result = applyTopNFilter(summaries, 100, TopNField.EncounterVolume); + expect(result).toHaveLength(3); + }); + + test('does not mutate the original array', () => { + const summaries = [makeSummary('A', 100, 1, 500), makeSummary('B', 300, 1, 200), makeSummary('C', 200, 1, 100)]; + const originalFirst = summaries[0].data.name; + applyTopNFilter(summaries, 2, TopNField.EncounterVolume); + expect(summaries[0].data.name).toBe(originalFirst); + }); +}); diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/CaseTypeFamilyFunctions.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/CaseTypeFamilyFunctions.ts new file mode 100644 index 0000000..8ddf1c9 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/CaseTypeFamilyFunctions.ts @@ -0,0 +1,140 @@ +import dayjs from 'dayjs'; +import React from 'react'; +import { ChipKey, ChipType, IFilterChipItem } from '../../shared/components/FilterBar/data/IFilterChipItem'; +import { IPeerGroupFilters } from '../../shared/configuration/IPeerGroupFilters'; +import { DATE_FORMAT } from '../../shared/constants'; +import IFilter from './IFilter'; +import { IEncounterGroupTreeGridData } from './IEncounterGroupSummary'; +import { TopNField } from './IExclusionCriteria'; + +/** + * Updates discharge date filter chips to use properly formatted dayjs objects. + * Finds the discharge date filter chip and maps its date range to dayjs objects with custom JSON serialization. + * @param filterChips - Array of filter chip items to process + * @returns The updated date range as dayjs objects, or undefined if no discharge date filter exists + */ +export const updateDischargeDate = (filterChips: IFilterChipItem[]): dayjs.Dayjs[] | undefined => { + // Early return if no chips to process + if (filterChips.length === 0) { + return undefined; + } + + // Find the discharge date filter chip + const dischargeDateFilter = filterChips.find((fc) => fc.key === ChipKey.DischargeDate); + if (!dischargeDateFilter) { + return undefined; + } + + // Validate date range exists and has at least 2 dates + const dateRange = dischargeDateFilter.dateRange; + if (!dateRange || dateRange.length < 2) { + return undefined; + } + + // Map dates to dayjs objects with custom JSON serialization + dischargeDateFilter.dateRange = dateRange.map((date) => { + const dateObj = dayjs(date); + dateObj.toJSON = () => dateObj.format(DATE_FORMAT); + return dateObj; + }); + + return dischargeDateFilter.dateRange; +}; + +/** + * Extracts and sets date range state from filter chips. + * Finds the date range filter chip and updates the provided state setters with start and end dates. + * @param filters - Filter object containing filter chip items + * @param setDateStart - State setter function for start date + * @param setDateEnd - State setter function for end date + */ +export const setDateStates = (filters: IFilter, setDateStart: (value: React.SetStateAction) => void, setDateEnd: (value: React.SetStateAction) => void) => { + const dateRangeFilter = filters.filterChipItems.find((fil) => fil.chipType === ChipType.dateRange); + if (dateRangeFilter && dateRangeFilter.dateRange) { + setDateStart(dateRangeFilter.dateRange[0]); + setDateEnd(dateRangeFilter.dateRange[1]); + } +}; + +/** + * Generates the appropriate header text for case type family grid based on family type. + * @param caseTypeFamilyType - Optional string indicating the specific case type family type + * @returns Formatted header string for the grid display + */ +export const caseTypeFamilyTypeHeader = (caseTypeFamilyType?: string) => + !caseTypeFamilyType ? 'Case Type Family / Cost Driver' : `Entity / ${caseTypeFamilyType ?? 'CPT'} / Primary Physician / Encounter`; + +/** + * Determines if peer group benchmarking columns should be displayed. + * Columns are shown when benchmarking is enabled and a valid peer group is selected. + * @param benchmarking - Whether benchmarking feature is enabled + * @param peerGroupFilters - Optional peer group filter configuration + * @returns true if benchmarking columns should be displayed + */ +export const peerGroupBenchmarkingColumns = (benchmarking: boolean, peerGroupFilters?: IPeerGroupFilters): boolean => peerGroupFilters?.peerGroupId !== 0 && benchmarking; + +/** + * Checks if data should be fetched based on current filter state. + * Returns false if no filters or only date filter is applied on first load. + * @param filters - Current filter state + * @param firstLoading - Whether this is the first load + * @returns true if data should be fetched + */ +export const shouldFetchData = (filters: IFilter | undefined, firstLoading: boolean): boolean => { + if (!filters?.filterChipItems || filters.filterChipItems.length === 0) { + return false; + } + + // Check if we ONLY have the date filter + // On first load, skip the fetch (just show the prompt to apply filters) + // When user clears filters mid-session, DO fetch to show all data + const onlyDateFilter = filters.filterChipItems.every((chip) => chip.key === ChipKey.DischargeDate); + if (onlyDateFilter && firstLoading) { + return false; + } + + if (onlyDateFilter) { + return true; + } + + return true; +}; + +/** + * Toggles grid reset key to force TreeGrid to reset its drill-down state. + * @param currentKey - Current grid reset key value + * @returns New grid reset key value + */ +export const toggleGridResetKey = (currentKey: number): number => { + return currentKey === 0 ? 1 : 0; +}; + +const getTopNValue = (s: IEncounterGroupTreeGridData, field: TopNField): number => { + switch (field) { + case TopNField.EncounterVolume: + return s.data.totalEncounters; + case TopNField.CostOeRatio: + return s.data.costOeRatio ?? 0; + case TopNField.IdentifiedVariation: + return s.data.identifiedVariation; + default: + // Fallback to a sentinel to avoid NaN in sort comparisons for unexpected TopNField values + return 0; + } +}; + +export const applyTopNFilter = (summaries: IEncounterGroupTreeGridData[], topN: number, topNField: TopNField): IEncounterGroupTreeGridData[] => + [...summaries].sort((a, b) => getTopNValue(b, topNField) - getTopNValue(a, topNField)).slice(0, topN); + +export const getTopNSortField = (field: TopNField): string => { + switch (field) { + case TopNField.EncounterVolume: + return 'totalEncounters'; + case TopNField.CostOeRatio: + return 'costOeRatio'; + case TopNField.IdentifiedVariation: + return 'identifiedVariation'; + default: + return 'totalEncounters'; + } +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IAddHeaderCellProps.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IAddHeaderCellProps.ts new file mode 100644 index 0000000..12e5d5c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IAddHeaderCellProps.ts @@ -0,0 +1,28 @@ +import { IDataHeaderCellProps } from '@strata/tempo/lib/dataheader/DataHeaderCell'; + +export interface IAddHeaderCellProps extends IDataHeaderCellProps { + key: string; +} + +export const addHeaderOptions = ( + key: string, + field: string, + header: string | React.ReactNode, + sortField?: string, + sortable?: true, + align?: 'left' | 'right' | 'center', + width?: number + // eslint-disable-next-line max-params +): IAddHeaderCellProps => { + return { + key, + field, + header, + sortable, + align: align ?? 'right', + width: width ?? 80, + sortField + } as IAddHeaderCellProps; +}; + +export default addHeaderOptions; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IAddTreeGridColumnNumberOptions.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IAddTreeGridColumnNumberOptions.ts new file mode 100644 index 0000000..ce07b4f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IAddTreeGridColumnNumberOptions.ts @@ -0,0 +1,40 @@ +import { NumberFormat } from '@strata/intl/lib'; + +export interface IAddTreeGridColumnNumberOptions { + key: string; + field: string; + header: string; + width: number; + align?: 'left' | 'right' | 'center'; + sortable?: boolean; + sortField?: string; + format?: NumberFormat; // from @strata/intl + body?: (rowData: any, column: any) => any; +} + +export const addTreeGridColumnNumberOptions = ( + key: string, + field: string, + header: string, + width?: number, + align?: 'left' | 'right' | 'center', + sortable?: true, + sortField?: string, + format?: NumberFormat, + body?: (rowData: any, column: any) => any + // eslint-disable-next-line max-params +) => { + return { + key, + field, + header, + width: width ?? 80, + align: align ?? 'right', + sortable: sortable ?? true, + sortField, + format, + body + } as IAddTreeGridColumnNumberOptions; +}; + +export default addTreeGridColumnNumberOptions; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/ICaseTypeFamilyDetailInfo.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/ICaseTypeFamilyDetailInfo.ts new file mode 100644 index 0000000..a0686a3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/ICaseTypeFamilyDetailInfo.ts @@ -0,0 +1,9 @@ +import { ICaseTypeFamilyMetricDetail } from './ICaseTypeFamilyMetricDetail'; +import { IEncounterGroupDetailTreeGridData } from './IEncounterGroupDetail'; + +export interface ICaseTypeFamilyDetailInfo { + caseTypeFamilyType: string; + caseTypeFamilyName: string; + metrics: ICaseTypeFamilyMetricDetail; + caseTypeFamilyDetails: IEncounterGroupDetailTreeGridData[]; +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/ICaseTypeFamilyMetricDetail.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/ICaseTypeFamilyMetricDetail.ts new file mode 100644 index 0000000..6ec0f24 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/ICaseTypeFamilyMetricDetail.ts @@ -0,0 +1,19 @@ +export interface ICaseTypeFamilyMetricDetail { + totalEncounters: number; + identifiedVariation: number; + encountersIncluded: number; + variationIncluded: number; + encountersExcluded: number; + variationExcluded: number; +} + +export const newMetricDetail = (data: Partial = {}): ICaseTypeFamilyMetricDetail => { + return { + totalEncounters: 0, + identifiedVariation: 0, + encountersIncluded: 0, + variationIncluded: 0, + encountersExcluded: 0, + variationExcluded: 0 + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IEncounterGroupDetail.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IEncounterGroupDetail.ts new file mode 100644 index 0000000..16a0e94 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IEncounterGroupDetail.ts @@ -0,0 +1,14 @@ +import IEncounterGroupSummary from './IEncounterGroupSummary'; + +export interface IEncounterGroupDetailTreeGridData { + key: string; + data: IEncounterGroupDetailRowData; + children: IEncounterGroupDetailTreeGridData[]; +} + +export interface IEncounterGroupDetailRowData extends IEncounterGroupSummary { + dischargeStatusCode: string; + dischargeStatusDescription: string; + medianGrossCharges: number; + isExcluded: boolean; +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IEncounterGroupSummary.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IEncounterGroupSummary.ts new file mode 100644 index 0000000..5174224 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IEncounterGroupSummary.ts @@ -0,0 +1,32 @@ +export interface IEncounterGroupTreeGridData { + key: string; + data: IEncounterGroupSummary; + leaf: boolean; + children: IEncounterGroupTreeGridData[]; +} + +export default interface IEncounterGroupSummary { + key: string; + id: number; + name: string; + totalEncounters: number; + totalVariationEncounters: number; + identifiedVariation: number; + includedEncounterCount: number; + mortalityRate: number; + medianLos: number; + averageLengthOfStay: number; + medianCost: number; + includedEncountersBelowMedian: number; + includedEncountersAboveMedian: number; + includedVariation: number; + excludedEncountersAboveMedian: number; + excludedVariation: number; + peerMedianALos: number; + peerMedianCost: number; + customerBenchmarkMedianCost: number; + customerBenchmarkALos: number; + costOeRatio?: number; + alosOeRatio?: number; + [field: string]: string | number | boolean | undefined; +} diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IExclusionCriteria.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IExclusionCriteria.ts new file mode 100644 index 0000000..5a10bb6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IExclusionCriteria.ts @@ -0,0 +1,100 @@ +export default interface IExclusionCriteria { + losExclusionType: ExclusionCriteriaType; + losExclusionValue: number; + costExclusionType: ExclusionCriteriaType; + costExclusionValue: number; + topN?: number | null; + topNField?: TopNField | null; +} + +export enum ExclusionCriteriaType { + None, + Mad, + Iqr +} + +export enum TopNField { + EncounterVolume = 'encounterVolume', + CostOeRatio = 'costOeRatio', + IdentifiedVariation = 'identifiedVariation' +} + +export const newExclusionCriteria = (): IExclusionCriteria => { + return { + losExclusionType: ExclusionCriteriaType.None, + losExclusionValue: 0, + costExclusionType: ExclusionCriteriaType.None, + costExclusionValue: 0, + topN: null, + topNField: null + }; +}; + +export const parseLosCostSelection = (selection: string) => { + switch (selection) { + case 'none0': + return { exclusionType: ExclusionCriteriaType.None, value: 0 }; + case 'mad2': + return { exclusionType: ExclusionCriteriaType.Mad, value: 2 }; + case 'mad3': + return { exclusionType: ExclusionCriteriaType.Mad, value: 3 }; + case 'iqr1': + return { exclusionType: ExclusionCriteriaType.Iqr, value: 1 }; + case 'iqr1.5': + return { exclusionType: ExclusionCriteriaType.Iqr, value: 1.5 }; + case 'iqr2': + return { exclusionType: ExclusionCriteriaType.Iqr, value: 2 }; + case 'iqr2.5': + return { exclusionType: ExclusionCriteriaType.Iqr, value: 2.5 }; + default: + return { exclusionType: ExclusionCriteriaType.None, value: 0 }; + } +}; + +export const parseLosTrimValue = (exclusionCriteria: IExclusionCriteria) => { + const exclusionType = ExclusionCriteriaType[exclusionCriteria.losExclusionType].toString().toLowerCase(); + return exclusionType + exclusionCriteria.losExclusionValue; +}; + +export const parseCostTrimValue = (exclusionCriteria: IExclusionCriteria) => { + const exclusionType = ExclusionCriteriaType[exclusionCriteria.costExclusionType].toString().toLowerCase(); + return exclusionType + exclusionCriteria.costExclusionValue; +}; + +// Info: actually, this function gets a string value instead of ExclusionCriteriaType +export const getExclusionCriteriaName = (exclusionType: ExclusionCriteriaType, exclusionValue: number) => { + if (exclusionType === null || exclusionType === undefined) return undefined; + return getExclusionCriteriaNameByString(exclusionType.toString(), exclusionValue); +}; + +export const getExclusionCriteriaNameByType = (exclusionType: ExclusionCriteriaType, exclusionValue: number) => { + if (exclusionType in ExclusionCriteriaType) { + return getExclusionCriteriaNameByString(ExclusionCriteriaType[exclusionType], exclusionValue); + } + return undefined; +}; + +export const getExclusionCriteriaNameByString = (exclusionType: string, exclusionValue: number) => { + if (exclusionType === null || exclusionType === undefined) return undefined; + const exclusion = exclusionType.toLowerCase() + exclusionValue; + switch (exclusion) { + case 'none0': + return 'No Exclusion'; + case 'mad': + return 'Median Absolute Deviation (MAD)'; + case 'mad2': + return 'Exclude > 2 Deviations'; + case 'mad3': + return 'Exclude > 3 Deviations'; + case 'iqr': + return 'Interquartile Range (IQR)'; + case 'iqr1': + return 'Exclude > 1 IQR'; + case 'iqr1.5': + return 'Exclude > 1.5 IQR'; + case 'iqr2': + return 'Exclude > 2 IQR'; + case 'iqr2.5': + return 'Exclude > 2.5 IQR'; + } +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IFilter.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IFilter.ts new file mode 100644 index 0000000..86d370c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IFilter.ts @@ -0,0 +1,35 @@ +import dayjs from 'dayjs'; +import { ChipKey, IFilterChipItem, newDateRangeChipItem } from '../../shared/components/FilterBar/data/IFilterChipItem'; +import { IConfiguration } from '../../shared/configuration/IConfiguration'; +import { RangeValue } from '@strata/tempo/lib/datepicker/RangePicker'; + +export default interface IFilter { + filterChipItems: IFilterChipItem[]; +} + +export const defaultFilter: IFilter = { + filterChipItems: [] +}; + +export const getSessionFilter = (configuration: IConfiguration, filterItem: IFilter): IFilter => { + const dischargeDateStart = dayjs(configuration.lastFiscalYearStartDate); + const dischargeDateEnd = dayjs(configuration.lastFiscalYearEndDate); + + // get from session if set or configuration + if (filterItem) { + console.info(filterItem); + const filters = filterItem; + if (filters?.filterChipItems) { + if (filters.filterChipItems.length === 0 || filters.filterChipItems.findIndex((fci) => fci.key === ChipKey.DischargeDate) === -1) { + const dateFilter = newDateRangeChipItem([dischargeDateStart, dischargeDateEnd] as RangeValue); + if (dateFilter) { + filters.filterChipItems.push(...[dateFilter]); + } + } + } + if (filters?.filterChipItems) { + return filters; + } + } + return { filterChipItems: [newDateRangeChipItem([dischargeDateStart, dischargeDateEnd] as RangeValue)] } as IFilter; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IHeatMapData.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IHeatMapData.ts new file mode 100644 index 0000000..b0a3454 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/IHeatMapData.ts @@ -0,0 +1,106 @@ +/** + * Simplified DTO for heatmap visualization containing only the essential + * benchmarking metrics needed for the UI. Supports both case type family + * and code-level (MS-DRG/APR-DRG/CPT) granularity. + */ +export interface IHeatMapData { + /** + * The display name for the row. Contains either the case type family name + * or the code value (MS-DRG/APR-DRG/CPT) depending on the granularity level. + */ + name: string; + + /** + * The cost driver name (e.g., "Pharmacy", "Laboratory"). + */ + costDriver: string; + + /** + * Customer's 50th percentile variable direct cost. + */ + customerMedianCost: number | null; + + /** + * Benchmark cohort's 50th percentile variable direct cost. + */ + benchmarkMedianCost: number | null; + + /** + * Pre-calculated O/E ratio (CustomerMedianCost / BenchmarkMedianCost). + * Null if benchmark is null or zero. + */ + costOeRatio: number | null; + + /** + * Customer's average length of stay. + */ + customerAlos: number | null; + + /** + * Benchmark cohort's 50th percentile average length of stay. + */ + benchmarkMedianAlos: number | null; + + /** + * Pre-calculated O/E ratio (CustomerMedianAlos / BenchmarkMedianAlos). + * Null if benchmark is null or zero. + */ + alosOeRatio: number | null; +} + +/** + * Pivoted heatmap row with cost drivers as dynamic columns. + * Each cost driver becomes a property containing its O/E ratio. + */ +export interface IHeatMapPivotedRow { + /** The case type family or code name */ + name: string; + /** Dynamic cost driver columns - key is cost driver name, value is O/E ratio */ + [costDriver: string]: number | null | string; +} + +/** + * Transforms flat heatmap data into pivoted rows with cost drivers as columns. + * @param data Raw heatmap data from API + * @returns Object containing pivoted rows and unique cost driver names + */ +export const pivotHeatMapData = (data: IHeatMapData[]): { rows: IHeatMapPivotedRow[]; costDrivers: string[] } => { + // Get unique cost drivers (these become columns) + const costDriverSet = new Set(); + data.forEach((item) => { + if (item.costDriver) { + costDriverSet.add(item.costDriver); + } + }); + const costDrivers = Array.from(costDriverSet).sort((a, b) => { + // 'Total' always comes first, and 'All Other' last + if (a === 'Total' || b === 'All Other') return -1; + if (b === 'Total' || a === 'All Other') return 1; + if (a === 'Total Filtered' || b === 'All Other') return -1; + if (b === 'Total Filtered' || a === 'All Other') return 1; + // All others sorted alphabetically + return a.localeCompare(b); + }); + + // Group by name (case type family or code) + const rowMap = new Map(); + + data.forEach((item) => { + if (!rowMap.has(item.name)) { + rowMap.set(item.name, { name: item.name }); + } + + const row = rowMap.get(item.name); + if (row && item.costDriver) { + if (!item.benchmarkMedianCost || item.benchmarkMedianCost === 0 || item.customerMedianCost === null) { + row[item.costDriver] = null; + } else { + row[item.costDriver] = item.customerMedianCost / item.benchmarkMedianCost; + } + } + }); + + const rows = Array.from(rowMap.values()); + + return { rows, costDrivers }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/ITreeGridColumnConfig.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/ITreeGridColumnConfig.ts new file mode 100644 index 0000000..6c3e031 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/ITreeGridColumnConfig.ts @@ -0,0 +1,68 @@ +import { NumberFormat } from '@strata/intl/lib'; +import { IEncounterGroupTreeGridData } from './IEncounterGroupSummary'; + +export const DEFAULT_GRID_PX_WIDTH = 100; +/** Column group for header grouping */ +export type ColumnGroup = 'name' | 'detail' | 'entirePopulation' | 'refinedPopulation' | 'benchmark' | 'excludedPopulation'; + +export interface ITreeGridColumnConfig { + key?: string; + field?: string; + header?: string | React.ReactNode; + /* Optional fixed width for the column in pixels. If not provided, a default of DEFAULT_GRID_PX_WIDTH width will be applied. */ + width?: number; + hidden?: boolean; + sortable?: boolean; + sortField?: string; + format?: NumberFormat; + body?: (rowData: IEncounterGroupTreeGridData) => React.ReactNode; + /* Alignment of the column. Default is 'right'. */ + align?: 'left' | 'center' | 'right'; + expander?: boolean; + /** Header group this column belongs to */ + group: ColumnGroup; + /** Whether this is a gap column (visual separator between groups) */ + isGap?: boolean; +} + +export const newTreeGridColumnConfig = (data: Partial & { group: ColumnGroup }): ITreeGridColumnConfig => { + return { + key: data.key, + field: data.field, + header: data.header, + width: data.width ?? DEFAULT_GRID_PX_WIDTH, + hidden: data.hidden ?? false, + sortable: data.sortable ?? true, + sortField: data.sortField, + format: data.format, + body: data.body, + align: data.align ?? 'right', + expander: data.expander ?? false, + group: data.group, + isGap: data.isGap ?? false + }; +}; + +export const isNumberColumn = (config: ITreeGridColumnConfig): config is ITreeGridColumnConfig & { format: NumberFormat } => { + return config.format !== undefined; +}; + +export const isGapColumn = (config: ITreeGridColumnConfig): boolean => { + return config.isGap === true; +}; + +export const isStandardColumn = (config: ITreeGridColumnConfig): boolean => { + return config.field !== undefined && config.header !== undefined && config.format === undefined && !config.isGap; +}; + +export const newGapColumn = (group: ColumnGroup): ITreeGridColumnConfig => { + return { + key: `gap-${group}`, + field: undefined, + header: undefined, + sortable: false, + hidden: false, + group: group, + isGap: true + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/explorationColumnHeaders.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/explorationColumnHeaders.ts new file mode 100644 index 0000000..b413ee1 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/explorationColumnHeaders.ts @@ -0,0 +1,34 @@ +/** + * Column header constants for the Exploration grids. + * Used in CaseTypeFamilyDetailGrid and CaseTypeFamilySummaryGrid. + */ + +// Detail Column +export const COLUMN_HEADER_INCLUDED_IN_POPULATION = 'Included in Population'; + +// Entire Population Group +export const COLUMN_HEADER_TOTAL_ENCOUNTERS = 'Total Encounters'; +export const COLUMN_HEADER_ENCOUNTERS_WITH_COST_DRIVER_VARIATION = 'Encounters with Cost Driver Variation'; +export const COLUMN_HEADER_IDENTIFIED_COST_DRIVER_VARIATION = 'Identified Cost Driver Variation'; + +// Benchmark Data Group +export const COLUMN_HEADER_CUSTOMER_MEDIAN_VARIABLE_DIRECT_COST = 'Customer Average Variable Direct Cost'; +export const COLUMN_HEADER_COHORT_MEDIAN_VARIABLE_DIRECT_COST = 'Peer Group Median Variable Direct Cost'; +export const COLUMN_HEADER_CUSTOMER_MEDIAN_ALOS = 'Customer ALOS'; +export const COLUMN_HEADER_COHORT_MEDIAN_ALOS = 'Peer Group Median ALOS'; +export const COLUMN_HEADER_OE_ALOS_RATIO = 'O / E ALOS Ratio'; +export const COLUMN_HEADER_OE_VARIABLE_DIRECT_COST_RATIO = 'O / E Variable Direct Cost Ratio'; + +// Refined Population Group +export const COLUMN_HEADER_ENCOUNTERS_INCLUDED = 'Encounters Included'; +export const COLUMN_HEADER_MORTALITY_RATE = 'Mortality Rate'; +export const COLUMN_HEADER_AVERAGE_LENGTH_OF_STAY = 'ALOS'; +export const COLUMN_HEADER_MEDIAN_GROSS_CHARGES = 'Median Gross Charges'; +export const COLUMN_HEADER_MEDIAN_VARIABLE_DIRECT_COST = 'Median Variable Direct Cost'; +export const COLUMN_HEADER_ENCOUNTERS_AT_OR_BELOW_MEDIAN_INCLUDED = 'Encounters At or Below Median Included'; +export const COLUMN_HEADER_ENCOUNTERS_ABOVE_MEDIAN_INCLUDED = 'Encounters Above Median Included'; +export const COLUMN_HEADER_COST_DRIVER_VARIATION_INCLUDED = 'Cost Driver Variation Included'; + +// Excluded Population Group +export const COLUMN_HEADER_ENCOUNTERS_ABOVE_MEDIAN_EXCLUDED = 'Encounters Above Median Excluded'; +export const COLUMN_HEADER_COST_DRIVER_VARIATION_EXCLUDED = 'Cost Driver Variation Excluded'; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/explorationService.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/explorationService.ts new file mode 100644 index 0000000..0d0b33d --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/explorationService.ts @@ -0,0 +1,90 @@ +import { appConfig, getSecureService } from '@strata/core/lib'; +import { adjustDatetimeToDateString } from '../../shared/utilities'; +import { BenchmarkingInsightsGranularity } from './BenchmarkingInsightsGranularity'; +import { ICaseTypeFamilyDetailInfo } from './ICaseTypeFamilyDetailInfo'; +import { IEncounterGroupTreeGridData } from './IEncounterGroupSummary'; +import IExclusionCriteria from './IExclusionCriteria'; +import IFilter from './IFilter'; +import { IHeatMapData } from './IHeatMapData'; + +const { httpGet, httpPost } = getSecureService(`${appConfig.apiUrl}v1/`); + +export const explorationService = { + getCaseTypeFamilySummaries: async (configurationGuid: string, exclusionCriteria: IExclusionCriteria, filters: IFilter, peerGroupId: number): Promise => { + return httpPost(`exploration/case-type-family-summary`, { + configurationGuid, + exclusionCriteria, + filters: adjustDatetimeToDateString({ ...filters }), + peerGroupId + }); + }, + + getCaseTypeFamilyCostDriverInfo: async ( + configurationGuid: string, + caseTypeFamilyId: string, + exclusionCriteria: IExclusionCriteria, + filters: IFilter, + peerGroupId: number + ): Promise => { + return httpPost(`exploration/case-type-family-cost-driver-info`, { + configurationGuid, + caseTypeFamilyId, + exclusionCriteria, + filters: adjustDatetimeToDateString({ ...filters }), + peerGroupId + }); + }, + + getCaseTypeFamilyDetails: async ( + configurationGuid: string, + caseTypeFamilyId: string, + costDriver: string, + excludedEncounters: number[], + includedEncounters: number[], + exclusionCriteria: IExclusionCriteria, + filters: IFilter, + codeType: number | null, + peerGroupId: number | null + ): Promise => { + return httpPost(`exploration/case-type-family-details`, { + configurationGuid, + caseTypeFamilyId, + costDriver, + excludedEncounters, + includedEncounters, + exclusionCriteria, + filters: adjustDatetimeToDateString({ ...filters }), + codeType, + peerGroupId + }); + }, + + isFlexibleExplorationInformationPageEnabled: async (): Promise => { + return httpGet(`exploration/flexible-exploration-information-page`); + }, + + /** + * Fetches heatmap data for benchmarking visualization. + * @param configurationGuid - The configuration GUID + * @param exclusionCriteria - Exclusion criteria for filtering + * @param filters - Filter settings + * @param peerGroupId - The peer group ID for benchmarking + * @param granularity - Row granularity: CaseTypeFamily, DrgCode, or PrimaryCpt + * @returns Array of heatmap data with cost driver metrics + */ + getHeatMapSummary: async ( + configurationGuid: string, + exclusionCriteria: IExclusionCriteria, + filters: IFilter, + peerGroupId: number, + granularity = BenchmarkingInsightsGranularity.CaseTypeFamily + ): Promise => { + return httpPost(`exploration/case-type-family-summary-heatmap`, { + configurationGuid, + exclusionCriteria, + filters: adjustDatetimeToDateString({ ...filters }), + peerGroupId, + granularity + }); + } +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/heatMapColors.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/heatMapColors.ts new file mode 100644 index 0000000..53bd7e6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/data/heatMapColors.ts @@ -0,0 +1,140 @@ +/** + * Heatmap color utilities using Tempo-approved color palette. + * + * Approved color palette for heatmap ONLY: + * - Red 500 (#D52117) with opacity 50%, 30% + * - Red 200 (#FEEDEC) + * - Green 500 (#008000) with opacity 50%, 30% + * - Green 200 (#EAFBEA) + * + * Text color: Black (default) + * Border color: Transparent (default) + * + * NOTE: The use of opacity to render additional shades of green and red + * has ONLY been approved for the Heatmap use case. Do not use elsewhere. + */ + +// Tempo color values (used for reference, computed RGBA values below) +const TEMPO_RED_500 = '#D52117'; +const TEMPO_RED_200 = '#FEEDEC'; +const TEMPO_GREEN_500 = '#008000'; +const TEMPO_GREEN_200 = '#EAFBEA'; + +const TEMPO_BLUE_700 = '#005F7A'; + +// Pre-computed RGBA values for opacity variants +export const HEATMAP_COLORS = { + // Red variants (O/E > 1 means customer is higher than benchmark - unfavorable) + red500: TEMPO_RED_500, + red500Opacity50: `rgba(213, 33, 23, 0.5)`, // Red 500 at 50% opacity + red500Opacity30: `rgba(213, 33, 23, 0.3)`, // Red 500 at 30% opacity + red200rgb: 'rgb(254, 237, 236)', // red200 in computed rgb format + red200: TEMPO_RED_200, + + // Green variants (O/E < 1 means customer is lower than benchmark - favorable) + green500: TEMPO_GREEN_500, + green500Opacity50: `rgba(0, 128, 0, 0.5)`, // Green 500 at 50% opacity + green500Opacity30: `rgba(0, 128, 0, 0.3)`, // Green 500 at 30% opacity + green200rgb: 'rgb(234, 251, 234)', // green200 in computed rgb format + green200: TEMPO_GREEN_200, + + blue700rgb: 'rgb(0, 95, 122)', // Border color for selected cells + blue700: TEMPO_BLUE_700, + + // No data + noDataRgb: 'rgba(0, 0, 0, 0)', + + // Neutral + transparent: 'transparent' +} as const; + +/** + * O/E Ratio thresholds for color mapping. + * Values > 1 indicate customer costs exceed benchmark (unfavorable - red) + * Values < 1 indicate customer costs below benchmark (favorable - green) + */ +export const OE_RATIO_THRESHOLDS = { + HIGH_RED: 1.2, // >= 1.20: Red 500 @ 50% + MEDIUM_RED: 1.1, // 1.10 - 1.19: Red 500 @ 30% + LOW_RED: 1.01, // 1.01 - 1.09: Red 200 + NEUTRAL: 1.0, // 1.00: Transparent + LOW_GREEN: 0.99, // 0.91 - 0.99: Green 200 + MEDIUM_GREEN: 0.9, // 0.81 - 0.90: Green 500 @ 30% + HIGH_GREEN: 0.8 // <= 0.80: Green 500 @ 50% +} as const; + +/** + * Gets the appropriate border color for a heatmap cell based on its background color. + * @param cellText The text content of the heatmap cell + * @returns CSS border color string + */ +export const getHeatMapBorderStyle = (cellText?: string): string => { + const hasValue = cellText && cellText !== ''; + + // Show blue border if cell has color or has a value + if (hasValue) { + return `2px solid ${HEATMAP_COLORS.blue700rgb}`; + } + + // Otherwise, no border (transparent) + return `2px solid ${HEATMAP_COLORS.transparent}`; +}; + +/** + * Gets the appropriate heatmap background color based on O/E ratio. + * @param oeRatio The O/E ratio value (customer / benchmark) + * @returns CSS background color string + */ +export const getHeatMapColor = (oeRatio: number | null | undefined): string => { + if (oeRatio === null || oeRatio === undefined) { + return HEATMAP_COLORS.transparent; + } + + const { HIGH_RED, MEDIUM_RED, NEUTRAL, HIGH_GREEN, MEDIUM_GREEN } = OE_RATIO_THRESHOLDS; + + // Customer exceeds benchmark (unfavorable - red shades) + if (oeRatio >= HIGH_RED) return HEATMAP_COLORS.red500Opacity50; + if (oeRatio >= MEDIUM_RED) return HEATMAP_COLORS.red500Opacity30; + if (oeRatio > NEUTRAL) return HEATMAP_COLORS.red200; + + // Neutral (exactly 1.0) + if (oeRatio === NEUTRAL) return HEATMAP_COLORS.transparent; + + // Customer below benchmark (favorable - green shades) + if (oeRatio <= HIGH_GREEN) return HEATMAP_COLORS.green500Opacity50; + if (oeRatio <= MEDIUM_GREEN) return HEATMAP_COLORS.green500Opacity30; + + // 0.91 - 0.99 + return HEATMAP_COLORS.green200; +}; + +/** + * Gets a human-readable description of what the O/E ratio color means. + * Useful for tooltips or accessibility. + */ +export const getHeatMapColorDescription = (oeRatio: number | null | undefined): string => { + if (oeRatio === null || oeRatio === undefined) { + return 'No data available'; + } + + if (oeRatio >= OE_RATIO_THRESHOLDS.HIGH_RED) { + return 'Significantly above benchmark (≥20%)'; + } + if (oeRatio >= OE_RATIO_THRESHOLDS.MEDIUM_RED) { + return 'Moderately above benchmark (10-19%)'; + } + if (oeRatio > OE_RATIO_THRESHOLDS.NEUTRAL) { + return 'Slightly above benchmark (1-9%)'; + } + if (oeRatio === OE_RATIO_THRESHOLDS.NEUTRAL) { + return 'At benchmark'; + } + if (oeRatio <= OE_RATIO_THRESHOLDS.HIGH_GREEN) { + return 'Significantly below benchmark (≥20%)'; + } + if (oeRatio <= OE_RATIO_THRESHOLDS.MEDIUM_GREEN) { + return 'Moderately below benchmark (10-19%)'; + } + + return 'Slightly below benchmark (1-9%)'; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useBenchmarkingBanner.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useBenchmarkingBanner.ts new file mode 100644 index 0000000..fce418c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useBenchmarkingBanner.ts @@ -0,0 +1,49 @@ +import { useClientScopedSessionStorage } from '../../shared/hooks/useClientScopedSessionStorage'; + +export interface IUseBenchmarkingBannerProps { + /** Whether benchmarking is enabled */ + benchmarking?: boolean; + /** + * Optional peer group ID. + * - When provided: banner shows only if benchmarking enabled AND peerGroupId !== 0 + * - When undefined: banner shows if benchmarking enabled (peer group check bypassed) + * Use undefined in contexts where peer group selection is not applicable (e.g., refinement drawer) + */ + peerGroupId?: number; +} + +export interface IUseBenchmarkingBannerResult { + /** Whether to show the benchmarking banner */ + showBanner: boolean; + /** Callback to dismiss the banner */ + onDismiss: () => void; +} + +/** + * Hook to manage benchmarking banner visibility state. + * Banner is shown when: + * - User hasn't dismissed it (stored in session storage) + * - Benchmarking is enabled + * - Peer group check passes: + * - If peerGroupId is provided: peerGroupId !== 0 + * - If peerGroupId is undefined: peer group check is bypassed + */ +export const useBenchmarkingBanner = (props: IUseBenchmarkingBannerProps): IUseBenchmarkingBannerResult => { + const { benchmarking = false, peerGroupId } = props; + + const [hideBenchmarkingBanner, setHideBenchmarkingBanner] = useClientScopedSessionStorage('hideBenchmarkingBanner', false); + + // When peerGroupId is undefined, skip peer group check (useful in contexts like refinement drawer) + // When peerGroupId is provided, require it to be non-zero + const peerGroupCheckPasses = peerGroupId === undefined || peerGroupId !== 0; + const showBanner = !hideBenchmarkingBanner && benchmarking && peerGroupCheckPasses; + + const onDismiss = () => { + setHideBenchmarkingBanner(true); + }; + + return { + showBanner, + onDismiss + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useCaseTypeFamilyGridBase.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useCaseTypeFamilyGridBase.ts new file mode 100644 index 0000000..380d5e0 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useCaseTypeFamilyGridBase.ts @@ -0,0 +1,199 @@ +import { useModal } from '@strata/tempo/lib/modal'; +import { usePageLoader } from '@strata/tempo/lib/pageloader/usePageLoader'; +import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { ChipKey, ChipType } from '../../shared/components/FilterBar/data/IFilterChipItem'; +import { useConfigurations } from '../../shared/configuration/ConfigurationContext'; +import { IConfiguration } from '../../shared/configuration/IConfiguration'; +import { IPeerGroupFilters } from '../../shared/configuration/IPeerGroupFilters'; +import { useFeatureFlags } from '../../shared/featureFlag/FeatureFlagContext'; +import { peerGroupBenchmarkingColumns } from '../data/CaseTypeFamilyFunctions'; +import { BenchmarkingInsightsGranularity } from '../data/BenchmarkingInsightsGranularity'; +import IExclusionCriteria from '../data/IExclusionCriteria'; +import IFilter from '../data/IFilter'; +import { IExplorationPopulation } from '../opportunities/data/IExplorationPopulation'; +import { useBenchmarkingBanner } from './useBenchmarkingBanner'; +import { useFilterManagement } from './useFilterManagement'; + +interface UseCaseTypeFamilyGridBaseOptions { + explorationPopulationData?: IExplorationPopulation; + explorationPopulationId?: string; + isBaseOptionsReadOnly: boolean; + onDrawerApply?: () => void; +} + +interface UseCaseTypeFamilyGridBaseResult { + // Common state + modal: ReturnType; + loading: boolean; + setLoading: (loading: boolean) => void; + firstLoading: boolean; + setFirstLoading: (loading: boolean) => void; + configuration: IConfiguration | undefined; + loadingConfiguration: boolean; + benchmarking: boolean; + gridResetKey: number; + setGridResetKey: Dispatch>; + manageColumnsVisible: boolean; + setManageColumnsVisible: (visible: boolean) => void; + + // Filter management + filters: IFilter | undefined; + exclusionCriteria: IExclusionCriteria | undefined; + peerGroupFilters: IPeerGroupFilters | undefined; + setFilters: (filters: IFilter) => void; + markDataAsLoaded: () => void; + renderFilterBar: (options?: { + filterId?: string; + excludedChips?: ChipKey[]; + hiddenChips?: ChipKey[]; + expandableLevels?: Array<{ text: string; value: number }>; + expandedLevelPos?: number; + setExpandedLevelPos?: (levelPos: number) => void; + onChangeEncounters?: (resolve: any) => void; + onApply?: (exclusionCriteria: IExclusionCriteria, filters: IFilter) => void | Promise; + hideTopNFilter?: boolean; + }) => React.ReactElement; + previousPeerGroupIdRef: React.MutableRefObject; + + // Feature flags + featureFlags: ReturnType; + + // Benchmarking banner + showBanner: boolean; + onDismissBanner: () => void; + + // Computed values + showPeerGroupBenchmarking: boolean; + + // Granularity selection + granularity: BenchmarkingInsightsGranularity; + setGranularity: Dispatch>; + + // Utility functions + handlePeerGroupChange: () => boolean; +} + +/** + * Base hook for Case Type Family grid components (Detail and Summary). + * Encapsulates common state management, filter handling, and feature flag logic. + */ +export const useCaseTypeFamilyGridBase = (options: UseCaseTypeFamilyGridBaseOptions = { isBaseOptionsReadOnly: false }): UseCaseTypeFamilyGridBaseResult => { + const { explorationPopulationData, explorationPopulationId, onDrawerApply, isBaseOptionsReadOnly } = options; + + // Common hooks + const modal = useModal(); + const { currentConfiguration: configuration, loading: loadingConfiguration } = useConfigurations(); + const { setLoading: setLoadingPage } = usePageLoader(); + const featureFlags = useFeatureFlags(); + + // Common state + const [loading, setLoading] = useState(false); + const [firstLoading, setFirstLoading] = useState(true); + const [benchmarking, setBenchmarking] = useState(false); + const [granularity, setGranularity] = useState(BenchmarkingInsightsGranularity.CaseTypeFamily); + const [gridResetKey, setGridResetKey] = useState(0); + const [manageColumnsVisible, setManageColumnsVisible] = useState(false); + + /** + * Centralized filter management with session persistence. + * Provides filter state (filters, exclusionCriteria, peerGroupFilters) for data fetching + * and a pre-configured FilterBar component with all handlers wired up. + */ + const { filters, exclusionCriteria, peerGroupFilters, setFilters, markDataAsLoaded, renderFilterBar } = useFilterManagement({ + configuration, + explorationPopulationData, + explorationPopulationId, + isFilterReadOnly: isBaseOptionsReadOnly, + onDrawerApply + }); + + const previousPeerGroupIdRef = useRef(peerGroupFilters?.peerGroupId ?? 0); + + // Set page loading based on first load state + useEffect(() => { + setLoadingPage(firstLoading || loadingConfiguration); + }, [firstLoading, loadingConfiguration, setLoadingPage]); + + // Initialize feature flags + useEffect(() => { + if (featureFlags.isEnabled && !featureFlags.loading) { + setBenchmarking(featureFlags.isEnabled('cibenchmarkingenabled')); + } + }, [featureFlags]); + + // Manage benchmarking banner visibility + const { showBanner, onDismiss: onDismissBanner } = useBenchmarkingBanner({ + benchmarking, + peerGroupId: peerGroupFilters?.peerGroupId + }); + + // Compute whether to show peer group benchmarking columns + const showPeerGroupBenchmarking = useMemo(() => { + return peerGroupBenchmarkingColumns(benchmarking, peerGroupFilters); + }, [benchmarking, peerGroupFilters]); + + /** + * Handle peer group changes - remove SPH criteria filters if switching to no peer group + * @returns true if filters were modified and component should exit early to re-run effect + */ + const handlePeerGroupChange = useCallback((): boolean => { + if (!filters || !peerGroupFilters) return false; + + const peerGroupChanged = previousPeerGroupIdRef.current !== peerGroupFilters.peerGroupId; + + if (peerGroupChanged) { + // Remove SPH criteria filters if switching to no peer group + if (peerGroupFilters.peerGroupId === 0 && filters.filterChipItems) { + const filteredChips = filters.filterChipItems.filter((chip) => chip.chipType !== ChipType.sphCriteria); + setFilters({ ...filters, filterChipItems: filteredChips }); + previousPeerGroupIdRef.current = peerGroupFilters.peerGroupId; + return true; // Signal to exit early + } + previousPeerGroupIdRef.current = peerGroupFilters.peerGroupId; + } + + return false; + }, [filters, peerGroupFilters, setFilters]); + + return { + // Common state + modal, + loading, + setLoading, + firstLoading, + setFirstLoading, + configuration, + loadingConfiguration, + benchmarking, + gridResetKey, + setGridResetKey, + manageColumnsVisible, + setManageColumnsVisible, + + // Filter management + filters, + exclusionCriteria, + peerGroupFilters, + setFilters, + markDataAsLoaded, + renderFilterBar, + previousPeerGroupIdRef, + + // Feature flags + featureFlags, + + // Benchmarking banner + showBanner, + onDismissBanner, + + // Computed values + showPeerGroupBenchmarking, + + // Granularity selection + granularity, + setGranularity, + + // Utility functions + handlePeerGroupChange + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useColumnManagement.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useColumnManagement.ts new file mode 100644 index 0000000..4fd8a6b --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useColumnManagement.ts @@ -0,0 +1,461 @@ +import { get } from 'lodash'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import BaseColumnManager from '../../shared/components/ColumnManager/BaseColumnManager'; +import { baseColumnManagerService } from '../../shared/components/ColumnManager/data/baseColumnManagerService'; +import { IMappedColumn } from '../../shared/components/IMappedColumn'; +import { ITreeGridColumnConfig } from '../data/ITreeGridColumnConfig'; + +export interface IUseColumnManagementOptions { + /** Primary grid name for column management */ + gridName: string; + /** Additional grid names to sync column visibility with */ + additionalGridNames?: string[]; + /** Function to generate column configs given managed columns and isFieldHidden callback */ + getColumnConfigs: (managedColumns: IMappedColumn[], isFieldHidden: (fieldName: string) => boolean, showPeerGroupBenchmarking?: boolean) => ITreeGridColumnConfig[]; + /** Whether to show peer group benchmarking columns */ + showPeerGroupBenchmarking?: boolean; + /** Optional column manager service for fetching and saving column layouts. Defaults to baseColumnManagerService */ + columnManagerService?: { + getColumnList: (name: string) => Promise; + setColumnList: (name: string, columns: IMappedColumn[]) => Promise; + }; + /** Optional configuration for the column manager panel */ + columnManagerPanel?: { + /** Title for the column manager panel. Defaults to 'Edit Column Layout' */ + title?: string; + /** Whether columns are sortable in the panel. Defaults to false */ + isSortable?: boolean; + /** Column names that cannot be hidden. Defaults to ['name'] */ + columnsNotToHide?: string[]; + }; + /** Name of the key column field that will be auto-included in managed columns if not explicitly defined. Defaults to 'key'. The column is added hidden by default and rendered with zero width for DOM accessibility. */ + keyColumnField?: string; + /** Whether to automatically include a key column for row identification. Defaults to true. Set to false to disable all key column auto-addition logic. */ + hasKeyColumnField?: boolean; +} + +export interface IUseColumnManagementResult { + /** Current managed columns with visibility state */ + managedColumns: IMappedColumn[]; + /** Whether column management is initialized */ + isInitialized: boolean; + /** Columns mapped for the column manager panel */ + mappedColumns: IMappedColumn[]; + /** Handler for when column manager applies changes */ + handleColumnApply: (columns: IMappedColumn[]) => void; + /** Get the current column configs with managed visibility applied */ + getColumnsWithVisibility: () => ITreeGridColumnConfig[]; + /** Utility function to check if a field is hidden in the current column configuration */ + isFieldHidden: (fieldName: string) => boolean; + /** Render function for the column manager panel */ + renderColumnManagerPanel: (props: IColumnManagerDialogProps) => JSX.Element; +} + +export interface IColumnManagerDialogProps { + /** Whether the dialog is visible */ + isVisible: boolean; + /** Callback when dialog is cancelled */ + onCancel: () => void; + /** Optional callback after columns are applied successfully */ + onApplyComplete?: () => void; +} + +/** + * Fetches column layout from server and merges with existing columns + * @internal + */ +const fetchColumns = async ( + name: string, + getColumns: IMappedColumn[], + columnManagerService: { + getColumnList: (name: string) => Promise; + } +): Promise => { + let fetchResult: IMappedColumn[] = getColumns.length > 0 ? [...getColumns] : []; + await columnManagerService + .getColumnList(name) + .then((columns) => { + columns.forEach((col) => { + const match = fetchResult.find((c) => c.name === col.name); + if (match) { + // Only update visibility for columns that exist in the grid definition + match.isHidden = col.isHidden; + } + // Ignore columns from server that don't exist in current grid definition + }); + }) + .catch(() => (fetchResult = [])); + return fetchResult; +}; + +/** + * Fetches and merges column layouts from multiple grid names + * @internal + */ +const fetchAndSetColumns = async ( + gridNames: string[], + getColumns: IMappedColumn[], + columnManagerService: { + getColumnList: (name: string) => Promise; + setColumnList: (name: string, columns: IMappedColumn[]) => Promise; + } +): Promise => { + let columnsResult: IMappedColumn[] = getColumns.length > 0 ? [...getColumns] : []; + for (const name of gridNames.filter((name) => name !== '')) { + columnsResult = await fetchColumns(name, columnsResult, columnManagerService); + } + await columnManagerService.setColumnList(gridNames[0], columnsResult); + return columnsResult; +}; + +/** + * Fetches column list from server and merges with passed columns, preserving order + * @param name - Grid name identifier + * @param passedColumns - Default column configuration + * @param columnManagerService - Service for fetching column layouts + * @returns Merged and sorted column list + */ +export const getColumnList = async ( + name: string, + passedColumns: IMappedColumn[], + columnManagerService: { getColumnList: (name: string) => Promise } = baseColumnManagerService +): Promise => { + let result: IMappedColumn[] = []; + if (passedColumns.length === 0) { + return result; + } + await columnManagerService.getColumnList(name).then((columns) => { + passedColumns.forEach((col: IMappedColumn) => { + const match = columns.find((c: IMappedColumn) => c.name === col.name); + if (match) { + result.push({ ...match }); + } else { + result.push({ ...col }); + } + }); + result.forEach((col: IMappedColumn) => { + const match = passedColumns.find((c: IMappedColumn) => c.name === col.name); + if (!match) { + col.index = undefined; + } + }); + result = result.filter((col: IMappedColumn) => col.index !== undefined).sort((a: IMappedColumn, b: IMappedColumn) => (a.index ?? 0) - (b.index ?? 0)); + }); + return result; +}; + +/** + * Custom hook for managing TreeGrid column visibility with persistent server-side storage. + * + * Features: + * - Persistent column visibility across sessions + * - Multi-grid synchronization (e.g., summary and detail grids share settings) + * - Integrated column manager dialog + * - Automatic fallback to default columns + * - Auto-inclusion of key column for row identification (hidden by default, zero-width for DOM access) + * + * @param options.gridName - Primary grid identifier for persistence + * @param options.additionalGridNames - Optional grids to sync column settings with + * @param options.getColumnConfigs - Function that returns column configurations + * @param options.showPeerGroupBenchmarking - Whether to show peer group benchmarking columns + * @param options.keyColumnField - Field name for auto-included key column (defaults to 'key') + * @param options.hasKeyColumnField - Whether to auto-include key column (defaults to true) + * @param options.columnManagerService - Optional custom service for fetching/saving columns + * @param options.columnManagerPanel - Optional dialog configuration (title, isSortable, columnsNotToHide) + * + * @returns managedColumns - Current columns with visibility state (includes auto-added key column) + * @returns isInitialized - Whether initialization is complete + * @returns mappedColumns - Columns for the column manager dialog + * @returns handleColumnApply - Apply column changes + * @returns getColumnsWithVisibility - Get columns with visibility applied (auto-adds key column if needed) + * @returns isFieldHidden - Check if a field is hidden + * @returns renderColumnManagerPanel - Render the column manager dialog + * + * @example + * const { getColumnsWithVisibility, renderColumnManagerPanel } = useColumnManagement({ + * gridName: 'mySummaryGrid', + * additionalGridNames: ['myDetailGrid'], + * getColumnConfigs: (columns, isFieldHidden) => [ + * { field: 'name', header: 'Name', hidden: isFieldHidden('name') }, + * { field: 'value', header: 'Value', hidden: isFieldHidden('value') } + * ] + * }); + */ +export const useColumnManagement = (options: IUseColumnManagementOptions): IUseColumnManagementResult => { + const { + gridName, + additionalGridNames = [], + getColumnConfigs, + showPeerGroupBenchmarking = false, + columnManagerService = baseColumnManagerService, + columnManagerPanel = {}, + keyColumnField = 'key', + hasKeyColumnField = true + } = options; + + const { title = 'Edit Column Layout', isSortable = false, columnsNotToHide = ['name'] } = columnManagerPanel; + + const [managedColumns, setManagedColumns] = useState([]); + const [defaultColumns, setDefaultColumns] = useState([]); + const [mappedColumns, setMappedColumns] = useState([]); + const [isInitialized, setIsInitialized] = useState(false); + + // Track initialization to ensure it only runs once on mount + const hasInitializedRef = useRef(false); + + // Track whether the key column was explicitly included (vs auto-added) + const keyColumnExplicitlyIncludedRef = useRef(false); + + // Track last fetch time to detect when to refresh columns from other grids + const lastFetchTimeRef = useRef(0); + + // Initialize columns only once on mount to prevent data loss from re-initialization + useEffect(() => { + const initializeColumns = async () => { + if (!gridName || hasInitializedRef.current) return; + + hasInitializedRef.current = true; + + try { + // Get default column configs to extract column metadata + // Pass a dummy isFieldHidden that always returns false for initialization + const defaultConfigs = getColumnConfigs([], () => false, showPeerGroupBenchmarking); + const passedColumns: IMappedColumn[] = defaultConfigs + .filter((col) => col.header !== undefined && col.header !== '' && !col.isGap) + .map((col, index) => ({ + index, + name: col.field ?? '', + heading: typeof col.header === 'string' ? col.header : 'Column', + isHidden: col.hidden ?? false, + isLocked: col.expander ?? col.field === 'name', + initiativeRollupColumnId: 0 + })); + + // Auto-add key column if enabled and not explicitly defined in getColumnConfigs + // The key column enables row identification for scrolling and selection + if (hasKeyColumnField) { + const hasKeyColumn = passedColumns.some((col) => col.name === keyColumnField); + keyColumnExplicitlyIncludedRef.current = hasKeyColumn; + if (!hasKeyColumn) { + passedColumns.push({ + index: passedColumns.length, + name: keyColumnField, + heading: 'Key', + isHidden: true, // Hidden by default, will be rendered with zero width + isLocked: false, + initiativeRollupColumnId: 0 + }); + } + } + + // Fetch saved column layouts from all grid names + const allGridNames = [gridName, ...additionalGridNames].filter((name) => name); + const columnsResult = await fetchAndSetColumns(allGridNames, passedColumns, columnManagerService); + + setManagedColumns(columnsResult); + setDefaultColumns(passedColumns); + setIsInitialized(true); + lastFetchTimeRef.current = Date.now(); + } catch (error) { + // Reset initialization flag on error to allow retry + hasInitializedRef.current = false; + throw error; + } + }; + + initializeColumns(); + // Only initialize once on mount - ignore changes to other dependencies + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [gridName]); + + // Update default columns when showPeerGroupBenchmarking changes + useEffect(() => { + if (!isInitialized) return; + + // Regenerate default columns with updated showPeerGroupBenchmarking + const updatedDefaultConfigs = getColumnConfigs([], () => false, showPeerGroupBenchmarking); + const updatedPassedColumns: IMappedColumn[] = updatedDefaultConfigs + .filter((col) => col.header !== undefined && col.header !== '' && !col.isGap) + .map((col, index) => ({ + index, + name: col.field ?? '', + heading: typeof col.header === 'string' ? col.header : 'Column', + isHidden: col.hidden ?? false, + isLocked: col.expander ?? col.field === 'name', + initiativeRollupColumnId: 0 + })); + + // Auto-add key column if enabled and not found in updated configs + if (hasKeyColumnField) { + const hasKeyColumn = updatedPassedColumns.some((col) => col.name === keyColumnField); + if (!hasKeyColumn) { + updatedPassedColumns.push({ + index: updatedPassedColumns.length, + name: keyColumnField, + heading: 'Key', + isHidden: true, // Hidden by default for auto-added columns + isLocked: false, + initiativeRollupColumnId: 0 + }); + } + } + + setDefaultColumns(updatedPassedColumns); + + // Merge columns preserving order from updated config and visibility settings from managed columns + const updatedManagedColumns: IMappedColumn[] = updatedPassedColumns.map((newCol) => { + const existingCol = managedColumns.find((c) => c.name === newCol.name); + if (existingCol) { + // Preserve visibility settings from existing column + return { + ...newCol, + isHidden: existingCol.isHidden + }; + } + // New column - use default visibility + return newCol; + }); + + // Only update if columns actually changed + if (JSON.stringify(updatedManagedColumns) !== JSON.stringify(managedColumns)) { + setManagedColumns(updatedManagedColumns); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showPeerGroupBenchmarking, isInitialized]); + + // Refresh columns when page becomes visible to sync changes from other grids + useEffect(() => { + const handleVisibilityChange = async () => { + if (!document.hidden && isInitialized && gridName) { + // Only refresh if it's been more than 100ms since last fetch to avoid rapid re-fetching + const timeSinceLastFetch = Date.now() - lastFetchTimeRef.current; + if (timeSinceLastFetch > 100) { + try { + // Fetch latest columns from all grid names to sync changes + const allGridNames = [gridName, ...additionalGridNames].filter((name) => name); + const currentColumns = managedColumns.length > 0 ? managedColumns : defaultColumns; + + if (currentColumns.length > 0) { + const refreshedColumns = await fetchAndSetColumns(allGridNames, currentColumns, columnManagerService); + + // Only update if columns actually changed + const columnsChanged = JSON.stringify(refreshedColumns) !== JSON.stringify(managedColumns); + if (columnsChanged) { + setManagedColumns(refreshedColumns); + } + + lastFetchTimeRef.current = Date.now(); + } + } catch (error) { + // Silently fail on refresh - don't disrupt user experience + console.error('Failed to refresh column layout:', error); + } + } + } + }; + + document.addEventListener('visibilitychange', handleVisibilityChange); + return () => { + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; + }, [isInitialized, gridName, additionalGridNames, managedColumns, defaultColumns, columnManagerService]); + + // Update mapped columns when managed columns change (for column manager dialog) + useEffect(() => { + if (managedColumns.length > 0) { + setMappedColumns(managedColumns); + } else if (defaultColumns.length > 0) { + setMappedColumns(defaultColumns); + } + }, [managedColumns, defaultColumns]); + + const handleColumnApply = useCallback( + async (columns: IMappedColumn[]) => { + // Update state first - React will batch these updates + setManagedColumns(columns); + setMappedColumns(columns); + + // Save to all grid names to keep them in sync + const allGridNames = [gridName, ...additionalGridNames].filter((name) => name); + await Promise.all(allGridNames.map((name) => columnManagerService.setColumnList(name, columns))); + }, + [gridName, additionalGridNames, columnManagerService] + ); + + /** + * Stable callback to check if a field is hidden in the current column configuration. + * Automatically bound to the current managed or default columns state. + */ + const isFieldHidden = useCallback( + (fieldName: string): boolean => { + const columnsToUse = managedColumns.length > 0 ? managedColumns : defaultColumns; + if (columnsToUse.length === 0) { + return false; + } + const managedColumn = columnsToUse.find((col) => get(col, 'name') === fieldName); + return managedColumn?.isHidden ?? false; + }, + [managedColumns, defaultColumns] + ); + + const getColumnsWithVisibility = useCallback((): ITreeGridColumnConfig[] => { + const columnsToUse = managedColumns.length > 0 ? managedColumns : defaultColumns; + const configs = getColumnConfigs(columnsToUse, isFieldHidden, showPeerGroupBenchmarking); + + // Key column is NOT rendered in the grid - it's only tracked in managedColumns for persistence + // TreeGrid uses the data structure's key property for row identification + + return configs; + }, [managedColumns, defaultColumns, getColumnConfigs, isFieldHidden, showPeerGroupBenchmarking]); + + /** + * Render function for the column manager dialog. + * Returns a BaseColumnManager component with all necessary props pre-configured. + * Auto-added key columns are excluded from the column manager UI. + */ + const renderColumnManagerPanel = useCallback( + ({ isVisible, onCancel, onApplyComplete }: IColumnManagerDialogProps) => { + const handleApply = async (columns: IMappedColumn[]) => { + // Re-add auto-added key column to persisted columns if enabled (it's not in the UI) + let columnsWithKey = columns; + if (hasKeyColumnField) { + const keyColumn = mappedColumns.find((col) => col.name === keyColumnField); + columnsWithKey = keyColumn && !keyColumnExplicitlyIncludedRef.current ? [...columns, keyColumn] : columns; + } + + // Update state and save to all synchronized grids + await handleColumnApply(columnsWithKey); + // Trigger completion callback after state updates + onApplyComplete?.(); + }; + + // Hide auto-added key column from column manager UI if enabled (only show explicitly defined columns) + const columnsForUI = hasKeyColumnField && !keyColumnExplicitlyIncludedRef.current ? mappedColumns.filter((col) => col.name !== keyColumnField) : mappedColumns; + + return React.createElement(BaseColumnManager, { + title, + isVisible, + isSortable, + name: gridName, + useStandardService: false, // We handle saving manually to sync all grids + columnsNotToHide, + getColumns: columnsForUI, + onApply: handleApply, + onCancel + }); + }, + [title, isSortable, gridName, columnsNotToHide, mappedColumns, handleColumnApply, keyColumnField, hasKeyColumnField] + ); + + return { + managedColumns, + isInitialized, + mappedColumns, + handleColumnApply, + getColumnsWithVisibility, + isFieldHidden, + renderColumnManagerPanel + }; +}; + +export default useColumnManagement; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useEncounterManagement.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useEncounterManagement.ts new file mode 100644 index 0000000..5b0c7a6 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useEncounterManagement.ts @@ -0,0 +1,238 @@ +import React from 'react'; +import { isEmpty, xor } from 'lodash'; +import { useCallback, useEffect, useState } from 'react'; +import { IEncounterGroupDetailTreeGridData } from '../data/IEncounterGroupDetail'; +import { IExplorationPopulation } from '../opportunities/data/IExplorationPopulation'; +import { TreeGridLevel } from './useTreeGridExpansion'; + +/** + * Configuration options for the encounter management hook. + */ +interface IUseEncounterManagementConfig { + /** Currently applied encounter exclusions from parent */ + encounterExclusionsApplied: number[]; + /** Callback to update applied exclusions in parent */ + setEncounterExclusionsApplied: (exclusions: number[]) => void; + /** Currently applied encounter inclusions from parent */ + encounterInclusionsApplied: number[]; + /** Callback to update applied inclusions in parent */ + setEncounterInclusionsApplied: (inclusions: number[]) => void; + /** Callback to set encounter IDs used in mapping */ + setMappingEncounterIds: (mappings: number[]) => void; + /** Exploration population data when editing an existing population */ + explorationPopulationData: IExplorationPopulation | undefined; + /** Callback to update expanded tree grid levels (resets to Entity on apply) */ + updateExpandedLevels: (levelPos: TreeGridLevel) => void; +} + +/** + * Return value from the encounter management hook. + */ +interface IUseEncounterManagementReturn { + /** Array of encounter IDs that should be excluded */ + encounterExclusions: number[]; + /** Array of encounter IDs that should be included */ + encounterInclusions: number[]; + /** Array of encounter IDs from initial grid load that were excluded */ + gridEncounterExclusions: number[]; + /** Whether the apply/cancel action bar should be visible */ + isApplyExclusionActionBarVisible: boolean; + /** Determines if an encounter is included based on current state */ + isEncounterIncluded: (encounterId: number) => boolean; + /** Handles toggling encounter inclusion/exclusion status */ + handleSetEncounterExclusion: (id: number, isIncluded: boolean) => void; + /** Initializes encounter exclusions from loaded grid data */ + setInitialEncounterExclusions: (groupDetails: IEncounterGroupDetailTreeGridData[]) => void; + /** Applies pending encounter exclusion/inclusion changes */ + handleApplyExclusions: () => Promise; + /** Cancels pending encounter exclusion/inclusion changes */ + handleCancelApplyExclusions: () => Promise; + /** Clears all encounter exclusion and inclusion state arrays */ + clearEncounterExclusionsInclusionsStates: () => void; + /** Updates encounter exclusion state (exposed for external updates) */ + setEncounterExclusions: React.Dispatch>; + /** Updates encounter inclusion state (exposed for external updates) */ + setEncounterInclusions: React.Dispatch>; +} + +/** + * Custom hook for managing encounter inclusion/exclusion state and operations. + * + * Handles the complex logic of tracking which encounters should be included or excluded + * from a population, managing both the initial loaded state and pending user changes, + * and coordinating with parent components to apply changes. + * + * @param config - Configuration options for encounter management + * @returns Object containing state and functions for encounter management + */ +export const useEncounterManagement = (config: IUseEncounterManagementConfig): IUseEncounterManagementReturn => { + const { + encounterExclusionsApplied, + setEncounterExclusionsApplied, + encounterInclusionsApplied, + setEncounterInclusionsApplied, + setMappingEncounterIds, + explorationPopulationData, + updateExpandedLevels + } = config; + + // ======================================================================== + // State + // ======================================================================== + + const [gridEncounterExclusions, setGridEncounterExclusions] = useState([]); + const [encounterExclusions, setEncounterExclusions] = useState([]); + const [encounterInclusions, setEncounterInclusions] = useState([]); + const [isApplyExclusionActionBarVisible, setIsApplyExclusionActionBarVisible] = useState(false); + + // ======================================================================== + // Callbacks + // ======================================================================== + + /** + * Initializes encounter exclusions from loaded grid data. + * Separates encounters into excluded and mapping (included) arrays. + */ + const setInitialEncounterExclusions = useCallback( + (groupDetails: IEncounterGroupDetailTreeGridData[]): void => { + if (!groupDetails) { + return; + } + + const excludedEncounters: number[] = []; + const mappingEncounters: number[] = []; + + groupDetails + ?.flatMap((x) => x.children.flatMap((y) => y.children.flatMap((z) => z.children))) + .forEach((detail) => { + if (detail.data.isExcluded) { + excludedEncounters.push(detail.data.id); + } else { + mappingEncounters.push(detail.data.id); + } + }); + + setGridEncounterExclusions(excludedEncounters); + setMappingEncounterIds(mappingEncounters); + }, + [setMappingEncounterIds] + ); + + /** + * Determines if an encounter is included in the population. + * Checks pending manual changes first, then falls back to initial loaded state. + */ + const isEncounterIncluded = useCallback( + (encounterId: number): boolean => { + if (encounterExclusions.includes(encounterId)) { + return false; + } + if (encounterInclusions.includes(encounterId)) { + return true; + } + return !gridEncounterExclusions.includes(encounterId); + }, + [encounterExclusions, encounterInclusions, gridEncounterExclusions] + ); + + /** + * Handles toggling encounter inclusion/exclusion status. + * Manages state transitions based on initial loaded state and user actions. + */ + const handleSetEncounterExclusion = useCallback( + (id: number, isIncluded: boolean): void => { + const wasInitiallyExcluded = gridEncounterExclusions.includes(id); + + if (isIncluded) { + setEncounterInclusions((prev) => { + const isInInclusionList = prev.includes(id); + return wasInitiallyExcluded && !isInInclusionList ? [...prev, id] : prev; + }); + setEncounterExclusions((prev) => { + const isInExclusionList = prev.includes(id); + return isInExclusionList ? prev.filter((x) => x !== id) : prev; + }); + } else { + setEncounterExclusions((prev) => { + const isInExclusionList = prev.includes(id); + return !wasInitiallyExcluded && !isInExclusionList ? [...prev, id] : prev; + }); + setEncounterInclusions((prev) => { + const isInInclusionList = prev.includes(id); + return isInInclusionList ? prev.filter((x) => x !== id) : prev; + }); + } + }, + [gridEncounterExclusions] + ); + + /** + * Applies pending encounter exclusion/inclusion changes and resets expanded levels. + */ + const handleApplyExclusions = useCallback(async (): Promise => { + setEncounterExclusionsApplied([...encounterExclusions]); + setEncounterInclusionsApplied([...encounterInclusions]); + setIsApplyExclusionActionBarVisible(false); + updateExpandedLevels(TreeGridLevel.Entity); + }, [encounterExclusions, encounterInclusions, setEncounterExclusionsApplied, setEncounterInclusionsApplied, updateExpandedLevels]); + + /** + * Cancels pending encounter exclusion/inclusion changes and reverts to applied state. + */ + const handleCancelApplyExclusions = useCallback(async (): Promise => { + setIsApplyExclusionActionBarVisible(false); + setEncounterExclusions([...encounterExclusionsApplied]); + setEncounterInclusions([...encounterInclusionsApplied]); + }, [encounterExclusionsApplied, encounterInclusionsApplied]); + + /** + * Clears all encounter exclusion and inclusion state arrays. + */ + const clearEncounterExclusionsInclusionsStates = useCallback((): void => { + setEncounterExclusionsApplied([]); + setEncounterInclusionsApplied([]); + setEncounterExclusions([]); + setEncounterInclusions([]); + }, [setEncounterExclusionsApplied, setEncounterInclusionsApplied]); + + // ======================================================================== + // Effects + // ======================================================================== + + // Initialize encounter exclusions/inclusions from saved population data + useEffect(() => { + if (!explorationPopulationData) { + return; + } + + setEncounterExclusions([...explorationPopulationData.manuallyExcludedEncounterIds]); + setEncounterInclusions([...explorationPopulationData.manuallyIncludedEncounterIds]); + setEncounterExclusionsApplied([...explorationPopulationData.manuallyExcludedEncounterIds]); + setEncounterInclusionsApplied([...explorationPopulationData.manuallyIncludedEncounterIds]); + }, [explorationPopulationData, setEncounterExclusionsApplied, setEncounterInclusionsApplied]); + + // Show action bar when pending encounter changes exist + useEffect(() => { + const hasUnappliedChanges = !isEmpty(xor(encounterInclusions, encounterInclusionsApplied)) || !isEmpty(xor(encounterExclusions, encounterExclusionsApplied)); + setIsApplyExclusionActionBarVisible(hasUnappliedChanges); + }, [encounterInclusions, encounterExclusions, encounterInclusionsApplied, encounterExclusionsApplied]); + + // ======================================================================== + // Return + // ======================================================================== + + return { + encounterExclusions, + encounterInclusions, + gridEncounterExclusions, + isApplyExclusionActionBarVisible, + isEncounterIncluded, + handleSetEncounterExclusion, + setInitialEncounterExclusions, + handleApplyExclusions, + handleCancelApplyExclusions, + clearEncounterExclusionsInclusionsStates, + setEncounterExclusions, + setEncounterInclusions + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useExplorationColumnConfigs.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useExplorationColumnConfigs.tsx new file mode 100644 index 0000000..d946781 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useExplorationColumnConfigs.tsx @@ -0,0 +1,439 @@ +import { appConfig } from '@strata/core/lib/config/appConfig'; +import intlService from '@strata/intl/lib'; +import Checkbox from '@strata/tempo/lib/checkbox/Checkbox'; +import Link from '@strata/tempo/lib/link/Link'; +import Text from '@strata/tempo/lib/text'; +import Tooltip from '@strata/tempo/lib/tooltip/Tooltip'; +import React, { useCallback } from 'react'; +import { IMappedColumn } from '../../shared/components/IMappedColumn'; +import { caseTypeFamilyTypeHeader } from '../data/CaseTypeFamilyFunctions'; +import { + COLUMN_HEADER_AVERAGE_LENGTH_OF_STAY, + COLUMN_HEADER_COHORT_MEDIAN_ALOS, + COLUMN_HEADER_COHORT_MEDIAN_VARIABLE_DIRECT_COST, + COLUMN_HEADER_COST_DRIVER_VARIATION_EXCLUDED, + COLUMN_HEADER_COST_DRIVER_VARIATION_INCLUDED, + COLUMN_HEADER_CUSTOMER_MEDIAN_ALOS, + COLUMN_HEADER_CUSTOMER_MEDIAN_VARIABLE_DIRECT_COST, + COLUMN_HEADER_ENCOUNTERS_ABOVE_MEDIAN_EXCLUDED, + COLUMN_HEADER_ENCOUNTERS_ABOVE_MEDIAN_INCLUDED, + COLUMN_HEADER_ENCOUNTERS_AT_OR_BELOW_MEDIAN_INCLUDED, + COLUMN_HEADER_ENCOUNTERS_INCLUDED, + COLUMN_HEADER_ENCOUNTERS_WITH_COST_DRIVER_VARIATION, + COLUMN_HEADER_IDENTIFIED_COST_DRIVER_VARIATION, + COLUMN_HEADER_INCLUDED_IN_POPULATION, + COLUMN_HEADER_MEDIAN_GROSS_CHARGES, + COLUMN_HEADER_MEDIAN_VARIABLE_DIRECT_COST, + COLUMN_HEADER_MORTALITY_RATE, + COLUMN_HEADER_OE_ALOS_RATIO, + COLUMN_HEADER_OE_VARIABLE_DIRECT_COST_RATIO, + COLUMN_HEADER_TOTAL_ENCOUNTERS +} from '../data/explorationColumnHeaders'; +import { getHeatMapColor } from '../data/heatMapColors'; +import { IEncounterGroupTreeGridData } from '../data/IEncounterGroupSummary'; +import { DEFAULT_GRID_PX_WIDTH, ITreeGridColumnConfig, newGapColumn, newTreeGridColumnConfig } from '../data/ITreeGridColumnConfig'; + +/** + * Interface for name column detail properties + */ +interface INameColumnDetailProps { + caseTypeFamilyId: string; + costDriver: string; + encounterExclusions: number[]; + encounterInclusions: number[]; + isExplorationInformationPageEnabled?: boolean; +} + +/** + * Interface for name column body properties + */ +interface INameColumnBodyProps { + rowData: IEncounterGroupTreeGridData; + details?: INameColumnDetailProps; +} + +/** + * Renders the name column body with appropriate links and styling. + * Handles both summary and detail views with drill-down navigation. + */ +const getNameColumnBody = (props: INameColumnBodyProps) => { + const contractsUrl = `${appConfig.contractsUrl}`; + const { rowData, details } = props; + const { caseTypeFamilyId, costDriver, encounterExclusions, encounterInclusions, isExplorationInformationPageEnabled } = details || {}; + + if (!details) { + return rowData.leaf ? {rowData.data.name} : {rowData.data.name}; + } + let column; + + if (rowData.children) { + if (rowData.key.includes('Entity')) { + column = 'entity'; + } else if (rowData.children[0]?.children) { + column = 'code'; + } else { + column = 'physician'; + } + } + + return rowData.children && rowData.children?.length !== 0 ? ( + isExplorationInformationPageEnabled ? ( + {rowData.data.name} + ) : ( + {rowData.data.name} + ) + ) : !rowData.children && (encounterExclusions?.map(Number).includes(Number(rowData.data.id)) || encounterInclusions?.map(Number).includes(Number(rowData.data.id))) ? ( + <> + + {rowData.data.name} + (manually changed) + + + ) : ( + + {rowData.data.name} + + ); +}; + +/** + * Renders mortality rate with tooltip for discharge status description. + * Shows either formatted percentage or discharge status code with description. + */ +const getMortalityRate = (rowData: any): React.ReactNode => { + return !rowData.data.dischargeStatusDescription ? ( + `${intlService.formatNumber(rowData.data.mortalityRate, 'percent')}` + ) : ( + + {rowData.data.dischargeStatusCode} + + ); +}; + +/** + * Shared ratio renderer with heat-map background styling. + */ +const HeatMapRatioCell = ({ ratio }: { ratio: number }) => ( +
+ {intlService.formatNumber(ratio, 'decimal')} +
+); + +/** + * Helper function to create name column configuration. + * This is the leftmost column with the case type/family name and expander. + */ +const nameColumnConfig = ({ caseTypeFamilyType, body }: { caseTypeFamilyType?: string; body?: (rowData: any) => JSX.Element }): ITreeGridColumnConfig => + newTreeGridColumnConfig({ + field: 'name', + header: caseTypeFamilyTypeHeader(caseTypeFamilyType), + width: 220, + align: 'left', + expander: true, + body: body, + group: 'name' + }); + +/** + * Enum defining where a column should be visible + */ +export enum ColumnVisibility { + /** Only visible in summary view */ + Summary = 'summary', + /** Only visible in detail view */ + Detail = 'detail', + /** Visible in both summary and detail views */ + Both = 'both' +} + +/** + * Type of view for column configuration + */ +export type ViewType = 'summary' | 'detail'; + +/** + * Configuration options for detail view + */ +export interface IDetailViewConfig { + caseTypeFamilyType?: string; + isExplorationInformationPageEnabled?: boolean; + caseTypeFamilyId?: string; + costDriver?: string; + encounterExclusions?: number[]; + encounterInclusions?: number[]; + isEncounterIncluded: (encounterId: number) => boolean; + handleSetEncounterExclusion: (id: number, isIncluded: boolean) => void; +} + +/** + * Props for useExplorationColumnConfigs hook + */ +export interface IUseExplorationColumnConfigsProps { + /** Type of view: 'summary' or 'detail' */ + viewType: ViewType; + /** Configuration for detail view (required if viewType is 'detail') */ + detailConfig?: IDetailViewConfig; + /** Controls whether the "Included in Population" column is editable */ + isIncludedInPopulationColumnReadOnly?: boolean; +} + +/** + * Shared hook for generating exploration grid column configurations. + * Provides unified column definitions for both summary and detail views with view-specific overrides. + * + * @param props - Configuration options including view type and detail-specific config + * @returns A function that generates column configurations based on visibility settings + */ +export const useExplorationColumnConfigs = (props: IUseExplorationColumnConfigsProps) => { + const { viewType, detailConfig, isIncludedInPopulationColumnReadOnly } = props; + + /** + * Generates TreeGrid column configurations with visibility and benchmarking options. + * Filters columns based on the current view type and applies view-specific customizations. + */ + const getColumnConfigs = useCallback( + (managedColumns: IMappedColumn[], isFieldHidden: (fieldName: string) => boolean, showPeerGroupBenchmarking = false): ITreeGridColumnConfig[] => { + const columnConfigs: ITreeGridColumnConfig[] = []; + const isDetailView = viewType === 'detail'; + + // Name column + if (isDetailView && detailConfig) { + columnConfigs.push( + nameColumnConfig({ + caseTypeFamilyType: detailConfig.caseTypeFamilyType, + body: (rowData) => + getNameColumnBody({ + rowData, + details: { + isExplorationInformationPageEnabled: detailConfig.isExplorationInformationPageEnabled, + caseTypeFamilyId: detailConfig.caseTypeFamilyId ?? '', + costDriver: detailConfig.costDriver ?? '', + encounterExclusions: detailConfig.encounterExclusions ?? [], + encounterInclusions: detailConfig.encounterInclusions ?? [] + } + }) + }) + ); + } else { + columnConfigs.push(nameColumnConfig({ body: (rowData) => getNameColumnBody({ rowData }) })); + } + + // Detail column (Included in Population checkbox) - Detail view only + if (isDetailView && detailConfig) { + columnConfigs.push({ + field: 'isExcluded', + header: COLUMN_HEADER_INCLUDED_IN_POPULATION, + width: 100, + align: 'center', + group: 'detail', + body: (rowData: any) => + !rowData.children ? ( + detailConfig.handleSetEncounterExclusion(rowData.data.id, e.target.checked)} + /> + ) : ( + <> + ) + }); + } + + // Entire Population group + columnConfigs.push( + { + field: 'totalEncounters', + header: COLUMN_HEADER_TOTAL_ENCOUNTERS, + hidden: isFieldHidden('totalEncounters'), + format: 'number', + group: 'entirePopulation', + ...(isDetailView && { body: (rowData) => (rowData.children ? `${intlService.formatNumber(rowData.data.totalEncounters, 'number')}` : '-') }) + }, + { + field: 'totalVariationEncounters', + header: COLUMN_HEADER_ENCOUNTERS_WITH_COST_DRIVER_VARIATION, + hidden: isFieldHidden('totalVariationEncounters'), + format: 'number', + group: 'entirePopulation', + ...(isDetailView && { body: (rowData) => (rowData.data.totalVariationEncounters ? `${intlService.formatNumber(rowData.data.totalVariationEncounters, 'number')}` : '-') }) + }, + { field: 'identifiedVariation', header: COLUMN_HEADER_IDENTIFIED_COST_DRIVER_VARIATION, hidden: isFieldHidden('identifiedVariation'), format: 'currency', group: 'entirePopulation' } + ); + + // Gap after Entire Population + columnConfigs.push(newGapColumn('entirePopulation')); + + // Benchmark Data group (peer benchmark columns) + if (showPeerGroupBenchmarking) { + columnConfigs.push( + { + field: 'customerBenchmarkMedianCost', + header: COLUMN_HEADER_CUSTOMER_MEDIAN_VARIABLE_DIRECT_COST, + hidden: isFieldHidden('customerBenchmarkMedianCost'), + format: 'currency', + group: 'benchmark', + ...(isDetailView && { body: (rowData) => (rowData.data.customerBenchmarkMedianCost ? `${intlService.formatNumber(rowData.data.customerBenchmarkMedianCost, 'currency')}` : '-') }) + }, + { + field: 'peerMedianCost', + header: COLUMN_HEADER_COHORT_MEDIAN_VARIABLE_DIRECT_COST, + hidden: isFieldHidden('peerMedianCost'), + format: 'currency', + group: 'benchmark', + ...(isDetailView && { body: (rowData) => (rowData.data.peerMedianCost ? `${intlService.formatNumber(rowData.data.peerMedianCost, 'currency')}` : '-') }) + }, + { + field: 'costOeRatio', + header: COLUMN_HEADER_OE_VARIABLE_DIRECT_COST_RATIO, + hidden: isFieldHidden('costOeRatio'), + format: 'decimal', + body: (rowData) => { + if (!rowData.data.costOeRatio || rowData.data.costOeRatio === 0) return '-'; + const ratio = rowData.data.costOeRatio; + return ; + }, + group: 'benchmark' + }, + { + field: 'customerBenchmarkALos', + header: COLUMN_HEADER_CUSTOMER_MEDIAN_ALOS, + hidden: isFieldHidden('customerBenchmarkALos'), + format: 'decimal', + body: isDetailView + ? (rowData) => (rowData.data.customerBenchmarkALos ? `${intlService.formatNumber(rowData.data.customerBenchmarkALos, 'decimal')}` : '-') + : (rowData) => (!rowData.leaf ? `${intlService.formatNumber(rowData.data.customerBenchmarkALos, 'decimal')}` : '-'), + group: 'benchmark' + }, + { + field: 'peerMedianALos', + header: COLUMN_HEADER_COHORT_MEDIAN_ALOS, + hidden: isFieldHidden('peerMedianALos'), + format: 'decimal', + body: isDetailView + ? (rowData) => (rowData.data.peerMedianALos ? `${intlService.formatNumber(rowData.data.peerMedianALos, 'decimal')}` : '-') + : (rowData) => (!rowData.leaf ? `${intlService.formatNumber(rowData.data.peerMedianALos, 'decimal')}` : '-'), + group: 'benchmark' + }, + { + field: 'alosOeRatio', + header: COLUMN_HEADER_OE_ALOS_RATIO, + hidden: isFieldHidden('alosOeRatio'), + format: 'decimal', + body: (rowData) => { + if (rowData.leaf || !rowData.data.alosOeRatio || rowData.data.alosOeRatio === 0) return '-'; + const ratio = rowData.data.alosOeRatio; + return ; + }, + group: 'benchmark' + }, + newGapColumn('benchmark') + ); + } + + // Refined Population group + columnConfigs.push( + { + field: 'includedEncounterCount', + header: COLUMN_HEADER_ENCOUNTERS_INCLUDED, + hidden: isFieldHidden('includedEncounterCount'), + format: 'number', + group: 'refinedPopulation', + ...(!isDetailView && { width: DEFAULT_GRID_PX_WIDTH }), + ...(isDetailView && { body: (rowData) => (rowData.children ? `${intlService.formatNumber(rowData.data.includedEncounterCount, 'number')}` : '-') }) + }, + { + field: 'mortalityRate', + header: COLUMN_HEADER_MORTALITY_RATE, + hidden: isFieldHidden('mortalityRate'), + format: 'percent', + group: 'refinedPopulation', + body: (rowData) => getMortalityRate(rowData), + ...(isDetailView && { sortField: 'dischargeStatusCode' }) + }, + { + field: 'averageLengthOfStay', + header: COLUMN_HEADER_AVERAGE_LENGTH_OF_STAY, + hidden: isFieldHidden('averageLengthOfStay'), + format: 'decimal', + group: 'refinedPopulation', + ...(!isDetailView && { body: (rowData) => (!rowData.leaf ? `${intlService.formatNumber(rowData.data.averageLengthOfStay, 'decimal')}` : '-') }) + } + ); + + // medianGrossCharges - Detail view only + if (isDetailView) { + columnConfigs.push({ + field: 'medianGrossCharges', + header: COLUMN_HEADER_MEDIAN_GROSS_CHARGES, + hidden: isFieldHidden('medianGrossCharges'), + format: 'currency', + group: 'refinedPopulation' + }); + } + + columnConfigs.push( + { + field: 'medianCost', + header: COLUMN_HEADER_MEDIAN_VARIABLE_DIRECT_COST, + hidden: isFieldHidden('medianCost'), + format: 'currency', + group: 'refinedPopulation', + ...(!isDetailView && { width: DEFAULT_GRID_PX_WIDTH }) + }, + { + field: 'includedEncountersBelowMedian', + header: COLUMN_HEADER_ENCOUNTERS_AT_OR_BELOW_MEDIAN_INCLUDED, + hidden: isFieldHidden('includedEncountersBelowMedian'), + format: 'number', + group: 'refinedPopulation', + ...(!isDetailView && { width: DEFAULT_GRID_PX_WIDTH }), + ...(isDetailView && { body: (rowData) => (rowData.children ? `${intlService.formatNumber(rowData.data.includedEncountersBelowMedian, 'number')}` : '-') }) + }, + { + field: 'includedEncountersAboveMedian', + header: COLUMN_HEADER_ENCOUNTERS_ABOVE_MEDIAN_INCLUDED, + hidden: isFieldHidden('includedEncountersAboveMedian'), + format: 'number', + group: 'refinedPopulation', + ...(!isDetailView && { width: DEFAULT_GRID_PX_WIDTH }), + ...(isDetailView && { body: (rowData) => (rowData.children ? `${intlService.formatNumber(rowData.data.includedEncountersAboveMedian, 'number')}` : '-') }) + }, + { + field: 'includedVariation', + header: COLUMN_HEADER_COST_DRIVER_VARIATION_INCLUDED, + hidden: isFieldHidden('includedVariation'), + format: 'currency', + group: 'refinedPopulation', + ...(!isDetailView && { width: DEFAULT_GRID_PX_WIDTH }) + } + ); + + // Gap after Refined Population + columnConfigs.push(newGapColumn('refinedPopulation')); + + // Excluded Population group + columnConfigs.push( + { + field: 'excludedEncountersAboveMedian', + header: COLUMN_HEADER_ENCOUNTERS_ABOVE_MEDIAN_EXCLUDED, + hidden: isFieldHidden('excludedEncountersAboveMedian'), + format: 'number', + group: 'excludedPopulation', + ...(isDetailView && { body: (rowData) => (rowData.children ? `${intlService.formatNumber(rowData.data.excludedEncountersAboveMedian, 'number')}` : '-') }) + }, + { field: 'excludedVariation', header: COLUMN_HEADER_COST_DRIVER_VARIATION_EXCLUDED, hidden: isFieldHidden('excludedVariation'), format: 'currency', group: 'excludedPopulation' } + ); + + return columnConfigs; + }, + [viewType, detailConfig] + ); + + return { getColumnConfigs }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useExplorationDataFetching.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useExplorationDataFetching.ts new file mode 100644 index 0000000..60ec524 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useExplorationDataFetching.ts @@ -0,0 +1,159 @@ +import { IFilterChipItem } from '../../shared/components/FilterBar/data/IFilterChipItem'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { IPeerGroupFilters } from '../../shared/configuration/IPeerGroupFilters'; +import { explorationService } from '../data/explorationService'; +import { ICaseTypeFamilyMetricDetail, newMetricDetail } from '../data/ICaseTypeFamilyMetricDetail'; +import { IEncounterGroupDetailTreeGridData } from '../data/IEncounterGroupDetail'; +import IExclusionCriteria from '../data/IExclusionCriteria'; +import IFilter from '../data/IFilter'; +import { IExplorationPopulation } from '../opportunities/data/IExplorationPopulation'; + +const hiddenFilterChips = ['CaseTypeFamilyID', 'CostDriver']; + +export interface IUseExplorationDataFetchingOptions { + configuration: any; + caseTypeFamilyId?: string; + costDriver?: string; + explorationPopulationData?: IExplorationPopulation; + peerGroupFilters: IPeerGroupFilters; + updateMetrics: (metrics: ICaseTypeFamilyMetricDetail) => void; + setCaseTypeFamilyName: (name: string) => void; + setSummaryFilters: (filters: any) => void; +} + +export interface IUseExplorationDataFetchingResult { + // Data + encounterGroupDetails?: IEncounterGroupDetailTreeGridData[]; + metrics: ICaseTypeFamilyMetricDetail; + caseTypeFamilyType: string; + isExplorationInformationPageEnabled: boolean; + + // Loading states + loading: boolean; + firstLoading: boolean; + + // Functions + fetchData: ( + drawerExclusionCriteria: IExclusionCriteria, + drawerFilters: IFilter | undefined, + encounterExclusions: number[], + encounterInclusions: number[] + ) => Promise; + useDataFetching: (filters: IFilter | undefined, exclusionCriteria: IExclusionCriteria, encounterExclusions: number[], encounterInclusions: number[]) => void; + setLoading: (loading: boolean) => void; +} + +export const useExplorationDataFetching = ({ + configuration, + caseTypeFamilyId, + costDriver, + explorationPopulationData, + peerGroupFilters, + updateMetrics, + setCaseTypeFamilyName, + setSummaryFilters +}: IUseExplorationDataFetchingOptions): IUseExplorationDataFetchingResult => { + // Data states + const [encounterGroupDetails, setEncounterGroupDetails] = useState(); + const [metrics, setMetrics] = useState(newMetricDetail); + const [caseTypeFamilyType, setCaseTypeFamilyType] = useState(''); + const [isExplorationInformationPageEnabled, setIsExplorationInformationPageEnabled] = useState(true); + + // Loading states + const [loading, setLoading] = useState(false); + const [firstLoading, setFirstLoading] = useState(true); + + // Refs for tracking changes + const previousPeerGroupIdRef = useRef(peerGroupFilters.peerGroupId); + + const fetchData = useCallback( + async ( + drawerExclusionCriteria: IExclusionCriteria, + drawerFilters: IFilter | undefined, + encounterExclusions: number[], + encounterInclusions: number[] + ): Promise => { + if (!configuration || !drawerFilters) return undefined; + + !firstLoading && setLoading(true); + try { + const filterChipItems = drawerFilters.filterChipItems.filter((item: IFilterChipItem) => !hiddenFilterChips.includes(item.key.toString())); + + await explorationService.isFlexibleExplorationInformationPageEnabled().then((result: boolean) => setIsExplorationInformationPageEnabled(result)); + const paramCaseTypeFamilyId = explorationPopulationData ? explorationPopulationData.caseTypeFamilyId.toString() : caseTypeFamilyId; + const paramCostDriver = explorationPopulationData ? explorationPopulationData.costDriver : costDriver; + const detailInfo = await explorationService.getCaseTypeFamilyDetails( + configuration.configurationGuid, + paramCaseTypeFamilyId ?? '0', + paramCostDriver ?? '', + encounterExclusions, + encounterInclusions, + drawerExclusionCriteria, + { + ...drawerFilters, + filterChipItems: filterChipItems + }, + explorationPopulationData ? explorationPopulationData.codeType : null, + peerGroupFilters.peerGroupId + ); + + setCaseTypeFamilyType(detailInfo.caseTypeFamilyType); + setEncounterGroupDetails(detailInfo.caseTypeFamilyDetails); + setMetrics(detailInfo.metrics); + setCaseTypeFamilyName(detailInfo.caseTypeFamilyName); + updateMetrics(detailInfo.metrics); + + return detailInfo.caseTypeFamilyDetails; + } catch { + // Prevent double error modal + return undefined; + } finally { + if (firstLoading) { + setFirstLoading(false); + } else { + setLoading(false); + } + } + }, + [configuration, caseTypeFamilyId, costDriver, explorationPopulationData, peerGroupFilters.peerGroupId, firstLoading, setCaseTypeFamilyName, updateMetrics] + ); + + // Effect to handle data fetching when dependencies change + useEffect(() => { + const peerGroupChanged = previousPeerGroupIdRef.current !== peerGroupFilters.peerGroupId; + + if (peerGroupChanged) { + // Remove SPH criteria filters if switching to no peer group + if (peerGroupFilters.peerGroupId === 0) { + // This logic should be handled by the parent component + // The hook shouldn't directly modify filters + } + previousPeerGroupIdRef.current = peerGroupFilters.peerGroupId; + } + }, [peerGroupFilters.peerGroupId]); + + // Function to trigger data fetching from component + const useDataFetching = useCallback( + (filters: IFilter | undefined, exclusionCriteria: IExclusionCriteria, encounterExclusions: number[], encounterInclusions: number[]) => { + fetchData(exclusionCriteria, filters, encounterExclusions, encounterInclusions); + }, + [fetchData] + ); + + return { + // Data + encounterGroupDetails, + metrics, + caseTypeFamilyType, + isExplorationInformationPageEnabled, + + // Loading states + loading, + firstLoading, + + // Functions + fetchData, + useDataFetching, + setLoading + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useFilterManagement.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useFilterManagement.ts new file mode 100644 index 0000000..bf7494c --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useFilterManagement.ts @@ -0,0 +1,243 @@ +import dayjs from 'dayjs'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import FilterBar from '../../shared/components/FilterBar/FilterBar'; +import { ChipKey } from '../../shared/components/FilterBar/data/IFilterChipItem'; +import { defaultPeerGroupFilter, IPeerGroupFilters } from '../../shared/configuration/IPeerGroupFilters'; +import { useClientScopedSessionStorage } from '../../shared/hooks/useClientScopedSessionStorage'; +import { setDateStates } from '../data/CaseTypeFamilyFunctions'; +import IExclusionCriteria, { newExclusionCriteria, parseCostTrimValue, parseLosCostSelection, parseLosTrimValue, TopNField } from '../data/IExclusionCriteria'; +import IFilter, { defaultFilter, getSessionFilter } from '../data/IFilter'; +import { IExplorationPopulation } from '../opportunities/data/IExplorationPopulation'; + +export interface IPickerItem { + text: string; + value: number; +} + +export interface IUseFilterManagementOptions { + configuration: any; + explorationPopulationData?: IExplorationPopulation; + explorationPopulationId?: string; + isFilterReadOnly: boolean; + onDrawerApply?: () => void; +} + +/** + * Manages exploration filter state with session storage persistence and population data loading. + * + * Handles filters, exclusion criteria (LOS/cost outliers), peer groups, and date ranges with + * automatic synchronization between session storage and local state. + * + * @param options.configuration - Configuration object for default filter settings + * @param options.explorationPopulationData - Optional saved population data to restore + * @param options.explorationPopulationId - Optional population ID to prevent config overrides + * + * @returns Object containing: + * - State: filters, exclusionCriteria, peerGroupFilters, dateStart, dateEnd, summaryFilters + * - Setters: setFilters, setExclusionCriteria + * - Handlers: handlePeerGroupFiltering, handleDrawerApply + * - Render: renderFilterBar + * + * @example + * const { filters, renderFilterBar, handleDrawerApply } = useFilterManagement({ + * configuration, + * explorationPopulationData: savedPopulation + * }); + */ +export const useFilterManagement = ({ configuration, explorationPopulationData, explorationPopulationId, isFilterReadOnly, onDrawerApply }: IUseFilterManagementOptions) => { + // Session storage states + const [summaryFilters, setSummaryFilters] = useClientScopedSessionStorage('summaryFilters', defaultFilter); + const [peerGroupFilters, setPeerGroupFilters] = useClientScopedSessionStorage('peerGroupFilters', defaultPeerGroupFilter); + const [costOutlierValue, setCostOutlierValue] = useClientScopedSessionStorage('costOutlierValue', ''); + const [losOutlierValue, setLosOutlierValue] = useClientScopedSessionStorage('losOutlierValue', ''); + const [topNValue, setTopNValue] = useClientScopedSessionStorage('topNValue', null); + const [topNFieldValue, setTopNFieldValue] = useClientScopedSessionStorage('topNFieldValue', null); + + // Local states + const [filters, setFilters] = useState(); + const [exclusionCriteria, setExclusionCriteria] = useState(newExclusionCriteria()); + const [dateStart, setDateStart] = useState(dayjs()); + const [dateEnd, setDateEnd] = useState(dayjs()); + + // Track initialization to prevent redundant updates + const configInitializedRef = useRef(false); + const populationInitializedRef = useRef(false); + const peerGroupInitializedRef = useRef(false); + const dataLoadedRef = useRef(false); + + // Initialize from session storage outliers + const setOutliersFromSession = useCallback((): void => { + const costSelection = parseLosCostSelection(costOutlierValue); + const losSelection = parseLosCostSelection(losOutlierValue); + setExclusionCriteria((prev) => ({ + ...prev, + losExclusionType: losSelection.exclusionType, + losExclusionValue: losSelection.value, + costExclusionType: costSelection.exclusionType, + costExclusionValue: costSelection.value, + topN: topNValue, + topNField: topNFieldValue + })); + }, [costOutlierValue, losOutlierValue, topNValue, topNFieldValue]); + + // Initialize filters from configuration (only once) + useEffect(() => { + if (configuration && !explorationPopulationId && !configInitializedRef.current) { + configInitializedRef.current = true; + + setOutliersFromSession(); + + const sessionFilters = getSessionFilter(configuration, summaryFilters); + setDateStates(sessionFilters, setDateStart, setDateEnd); + setSummaryFilters(sessionFilters); + setFilters(sessionFilters); + } + }, [configuration, explorationPopulationId, summaryFilters, setSummaryFilters, setOutliersFromSession]); + + // Initialize from exploration population data (only once) + useEffect(() => { + if (explorationPopulationData && !populationInitializedRef.current) { + populationInitializedRef.current = true; + + const populationFilters = { + filterChipItems: JSON.parse(explorationPopulationData.filtersJSON) + }; + + setFilters(populationFilters); + setSummaryFilters(populationFilters); + + const exclusCriteria = { + losExclusionType: explorationPopulationData.losExclusionType, + losExclusionValue: explorationPopulationData.losExclusionValue, + costExclusionType: explorationPopulationData.costExclusionType, + costExclusionValue: explorationPopulationData.costExclusionValue + }; + + setExclusionCriteria(exclusCriteria); + setLosOutlierValue(parseLosTrimValue(exclusCriteria)); + setCostOutlierValue(parseCostTrimValue(exclusCriteria)); + } + }, [explorationPopulationData, setSummaryFilters, setCostOutlierValue, setLosOutlierValue]); + + const handlePeerGroupFiltering = useCallback( + (newPeerGroupFilters: IPeerGroupFilters) => { + // On first call, initialize the peer group (from FilterBar's initial setup) + if (!peerGroupInitializedRef.current) { + peerGroupInitializedRef.current = true; + setPeerGroupFilters(newPeerGroupFilters); + return; + } + + // After initialization, always allow peer group updates to flow through so column visibility can update + // The actual data fetch is guarded in the grid components' useEffects + if (peerGroupFilters?.peerGroupId !== newPeerGroupFilters.peerGroupId) { + setPeerGroupFilters(newPeerGroupFilters); + } + }, + [peerGroupFilters, setPeerGroupFilters] + ); + + /** + * Callback for consuming components to signal that initial data load is complete. + * After this is called, peer group changes will be allowed to trigger data reloads. + */ + const markDataAsLoaded = useCallback(() => { + dataLoadedRef.current = true; + peerGroupInitializedRef.current = true; + }, []); + + const handleDrawerApply = useCallback( + (drawerExclusionCriteria: IExclusionCriteria, drawerFilters: IFilter) => { + // Update filters directly with what came from the drawer + setFilters(drawerFilters); + setSummaryFilters(drawerFilters); + + // Update exclusion criteria + setExclusionCriteria(drawerExclusionCriteria); + + // Persist outlier values to session storage + setLosOutlierValue(parseLosTrimValue(drawerExclusionCriteria)); + setCostOutlierValue(parseCostTrimValue(drawerExclusionCriteria)); + + // Persist Top N values to session storage + setTopNValue(drawerExclusionCriteria.topN ?? null); + setTopNFieldValue(drawerExclusionCriteria.topNField ?? null); + + // Call callback if provided (to perform post 'apply' actions) + onDrawerApply?.(); + }, + [setSummaryFilters, setLosOutlierValue, setCostOutlierValue, setTopNValue, setTopNFieldValue, onDrawerApply] + ); + + /** + * Renders the FilterBar component with all filter management state and handlers pre-wired + * + * @param options - Configuration options for the FilterBar + * @param options.filterId - Optional filter identifier + * @param options.excludedChips - Optional array of chip keys to exclude from display + * @param options.hiddenChips - Optional array of chip keys to hide + * @param options.expandableLevels - Optional array of expandable level picker items (for detail view) + * @param options.expandedLevelPos - Optional current expanded level position + * @param options.setExpandedLevelPos - Optional callback to update expanded level position + * @param options.onChangeEncounters - Optional callback when encounters change + * @param options.onApply - Optional custom onApply handler (overrides default) + * @returns FilterBar component configured with current filter state + */ + const renderFilterBar = useCallback( + (options?: { + filterId?: string; + excludedChips?: ChipKey[]; + hiddenChips?: ChipKey[]; + expandableLevels?: IPickerItem[]; + expandedLevelPos?: number; + setExpandedLevelPos?: (levelPos: number) => void; + onChangeEncounters?: (resolve: any) => void; + onApply?: (exclusionCriteria: IExclusionCriteria, filters: IFilter) => void | Promise; + hideTopNFilter?: boolean; + }): React.ReactElement => { + return React.createElement(FilterBar, { + filterId: options?.filterId ?? '', + isFilterReadOnly: isFilterReadOnly, + filterChips: filters?.filterChipItems ?? [], + excludedChips: options?.excludedChips ?? [], + hiddenChips: options?.hiddenChips ?? [], + dateStart, + dateEnd, + exclusionCriteria, + onPeerGroupFiltersChange: handlePeerGroupFiltering, + onApply: options?.onApply ?? handleDrawerApply, + hideTopNFilter: options?.hideTopNFilter, + ...(options?.expandableLevels && { expandableLevels: options.expandableLevels }), + ...(options?.expandedLevelPos !== undefined && { expandedLevelPos: options.expandedLevelPos }), + ...(options?.setExpandedLevelPos && { setExpandedLevelPos: options.setExpandedLevelPos }), + ...(options?.onChangeEncounters && { onChangeEncounters: options.onChangeEncounters }) + }); + }, + [isFilterReadOnly, filters, dateStart, dateEnd, exclusionCriteria, handlePeerGroupFiltering, handleDrawerApply] + ); + + return useMemo( + () => ({ + // States + filters, + exclusionCriteria, + peerGroupFilters, + dateStart, + dateEnd, + summaryFilters, + + // Setters + setFilters, + setExclusionCriteria, + + // Handlers + handlePeerGroupFiltering, + handleDrawerApply, + markDataAsLoaded, + + // Render functions + renderFilterBar + }), + [filters, exclusionCriteria, peerGroupFilters, dateStart, dateEnd, summaryFilters, handlePeerGroupFiltering, handleDrawerApply, markDataAsLoaded, renderFilterBar] + ); +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useRefinementDrawer.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useRefinementDrawer.ts new file mode 100644 index 0000000..2a9a421 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useRefinementDrawer.ts @@ -0,0 +1,373 @@ +import { DropDownValue } from '@strata/tempo/lib/dropdown/DropDown'; +import { RangeValue } from '@strata/tempo/lib/datepicker/RangePicker'; +import { ITreeNode, Key } from '@strata/tempo/lib/tree/Tree'; +import dayjs from 'dayjs'; +import { isEqual } from 'lodash'; +import { useEffect, useMemo, useState } from 'react'; +import { + ChipKey, + ChipType, + defaultFilterBarItemsExcluded, + IFilterChipItem, + isDualFilterChip, + newDateRangeChipItem, + OperatorType, + sphCriteriaFilterItems +} from '../../shared/components/FilterBar/data/IFilterChipItem'; +import { DATE_FORMAT } from '../../shared/constants'; +import { useClientScopedSessionStorage } from '../../shared/hooks/useClientScopedSessionStorage'; +import { useSystemSettings } from '../../shared/systemSettings/SystemSettingsContext'; +import IExclusionCriteria, { parseCostTrimValue, parseLosCostSelection, parseLosTrimValue, TopNField } from '../data/IExclusionCriteria'; + +export interface IUseRefinementDrawerProps { + isVisible: boolean; + filterChips: IFilterChipItem[]; + excludedChips: ChipKey[]; + dateRange: [dayjs.Dayjs, dayjs.Dayjs]; + exclusionCriteria: IExclusionCriteria; + hasBenchmarking?: boolean; + setDateRange: (dateRange: [dayjs.Dayjs, dayjs.Dayjs]) => void; +} + +export interface IUseRefinementDrawerResult { + // State + filters: IFilterChipItem[]; + drawerChipFilters: IFilterChipItem[] | undefined; + drawerExclusionCriteria: IExclusionCriteria; + activeFilterId: React.Key; + + // Filter popover state + isFilterPopoverVisible: boolean; + selectedKeys: Key[]; + selectedNode: ITreeNode; + + // SPH popover state + isSphPopoverVisible: boolean; + sphSelectedKeys: Key[]; + sphSelectedNode: ITreeNode; + sphCustomItems: IFilterChipItem[]; + + // Handlers - Filter popover + handleOpenChange: (isOpen: boolean) => void; + handleClose: () => void; + handleSelect: (selectedKeys: Key[], node: ITreeNode) => void; + handleAddFilter: () => void; + + // Handlers - SPH popover + handleSphOpenChange: (isOpen: boolean) => void; + handleSphClose: () => void; + handleSphSelect: (selectedKeys: Key[], node: ITreeNode) => void; + handleAddSphFilter: () => void; + + // Handlers - Chips + handleChipOpenChange: (isOpen: boolean) => void; + handleFilterChange: (filter: IFilterChipItem) => void; + handleChipDelete: (chipId: React.Key) => void; + handleChipClick: (chipId: React.Key) => void; + + // Handlers - Exclusion criteria + handleALOSChange: (value: DropDownValue) => void; + handleTDVCChange: (value: DropDownValue) => void; + handleTopNFieldChange: (value: TopNField | null) => void; + handleTopNChange: (value: number | null) => void; + + // Apply handler + getApplyPayload: () => { drawerExclusionCriteria: IExclusionCriteria; activeFilters: IFilterChipItem[] }; +} + +/** + * Custom hook to manage RefinementOptionsDrawer state and business logic. + * Handles filter management, popover states, exclusion criteria, and chip synchronization. + */ +export const useRefinementDrawer = (props: IUseRefinementDrawerProps): IUseRefinementDrawerResult => { + const { isVisible, filterChips, excludedChips, dateRange, exclusionCriteria, hasBenchmarking, setDateRange } = props; + + // Session storage for outlier values + const [, setCostOutlierValue] = useClientScopedSessionStorage('costOutlierValue', ''); + const [, setLosOutlierValue] = useClientScopedSessionStorage('losOutlierValue', ''); + + const { systemSettings } = useSystemSettings(); + const isUsingAprDrg = systemSettings?.flexibleExplorationInpatientCTF ?? false; + + // Filter state + const filters = useMemo(() => defaultFilterBarItemsExcluded(excludedChips, false, isUsingAprDrg), [excludedChips, isUsingAprDrg]); + const [drawerChipFilters, setDrawerChipFilters] = useState(); + const [drawerExclusionCriteria, setDrawerExclusionCriteria] = useState(exclusionCriteria); + const [activeFilterId, setActiveFilterId] = useState(''); + + // Filter popover state + const [isFilterPopoverVisible, setIsFilterPopoverVisible] = useState(false); + const [selectedKeys, setSelectedKeys] = useState([]); + const [selectedNode, setSelectedNode] = useState({} as ITreeNode); + + // SPH popover state + const [isSphPopoverVisible, setIsSphPopoverVisible] = useState(false); + const [sphSelectedKeys, setSphSelectedKeys] = useState([]); + const [sphSelectedNode, setSphSelectedNode] = useState({} as ITreeNode); + const sphCustomItems = sphCriteriaFilterItems(); + + // Sync drawer state with props when drawer becomes visible + useEffect(() => { + if (isVisible) { + const filtersCopy = filterChips.map((fil) => ({ ...fil })); + setDrawerChipFilters(filtersCopy); + } + }, [filterChips, isVisible]); + + useEffect(() => { + if (isVisible) { + setDrawerExclusionCriteria({ ...exclusionCriteria }); + } + }, [exclusionCriteria, isVisible]); + + // O/E Variable Direct Cost requires a peer group. If the drawer is open when the peer group + // is deselected, clear the Top N field so a stale O/E selection isn't carried through on apply. + useEffect(() => { + if (!hasBenchmarking) { + setDrawerExclusionCriteria((prev) => (prev.topNField === TopNField.CostOeRatio ? { ...prev, topNField: null, topN: null } : prev)); + } + }, [hasBenchmarking]); + + // Sync benchmarking criteria with population criteria + useEffect(() => { + if (!hasBenchmarking || !drawerChipFilters) return; + + const populationCriteriaDualFilters = drawerChipFilters.filter((chip) => chip.chipType !== ChipType.sphCriteria && isDualFilterChip(chip)); + const benchmarkingFilters = drawerChipFilters.filter((chip) => chip.chipType === ChipType.sphCriteria); + + // Remove readOnly benchmarking filters that no longer have corresponding population filters + const benchmarkingFiltersToRemove = benchmarkingFilters.filter((benchmarkChip) => { + if (!benchmarkChip.isReadOnly) return false; + return !populationCriteriaDualFilters.some((popFilter) => `${popFilter.key}_benchmark` === benchmarkChip.key.toString()); + }); + + const drawerFilters = [...drawerChipFilters]; + let hasChanges = benchmarkingFiltersToRemove.length > 0; + + populationCriteriaDualFilters.forEach((filter) => { + const benchmarkFilterKey = `${filter.key}_benchmark` as unknown as ChipKey; + const benchmarkFilterIndex = drawerFilters.findIndex((chip) => chip.key === benchmarkFilterKey); + + if (benchmarkFilterIndex >= 0) { + // Update existing benchmark chip in place + const current = drawerFilters[benchmarkFilterIndex]; + if (!isEqual(current.filters, filter.filters) || current.operator !== filter.operator) { + drawerFilters[benchmarkFilterIndex] = { + ...current, + filters: filter.filters, + operator: filter.operator + }; + hasChanges = true; + } + } else { + // Add new benchmark chip + drawerFilters.push({ + ...filter, + key: benchmarkFilterKey, + chipType: ChipType.sphCriteria, + isReadOnly: true + }); + hasChanges = true; + } + }); + + // Apply all changes at once + if (hasChanges) { + setDrawerChipFilters(drawerFilters.filter((chip) => !benchmarkingFiltersToRemove.some((toRemove) => toRemove.key === chip.key))); + } + }, [drawerChipFilters, hasBenchmarking]); + + // Filter popover handlers + const handleOpenChange = (isOpen: boolean) => { + if (isOpen) { + setIsFilterPopoverVisible(isOpen); + } else { + handleClose(); + } + }; + + const handleClose = () => { + setSelectedNode({} as ITreeNode); + setSelectedKeys([]); + setIsFilterPopoverVisible(false); + }; + + const handleSelect = (selectedKeys: Key[], node: ITreeNode) => { + setSelectedKeys(selectedKeys); + setSelectedNode(node); + }; + + const handleAddFilter = () => { + setIsFilterPopoverVisible(false); + const filter = filters.find((cf) => ChipKey[cf.key] === (selectedNode.key as string)); + if (filter) { + const newFilter = Object.assign({}, filter); + const newFilters = [...(drawerChipFilters ?? []), newFilter]; + setDrawerChipFilters(newFilters); + setActiveFilterId(filter.key); + } + }; + + // SPH popover handlers + const handleSphOpenChange = (isOpen: boolean) => { + if (isOpen) { + setIsSphPopoverVisible(true); + } else { + handleSphClose(); + } + }; + + const handleSphClose = () => { + setSphSelectedNode({} as ITreeNode); + setSphSelectedKeys([]); + setIsSphPopoverVisible(false); + }; + + const handleSphSelect = (selectedKeys: Key[], node: ITreeNode) => { + setSphSelectedKeys(selectedKeys); + setSphSelectedNode(node); + }; + + const handleAddSphFilter = () => { + if (sphSelectedKeys.length === 0) return; + const match = sphCustomItems.find((it) => ChipKey[it.key] === (sphSelectedNode.key as string)); + if (!match) return; + + const newFilter = { ...match } as IFilterChipItem; + const newFilters = [...(drawerChipFilters ?? []), newFilter]; + setDrawerChipFilters(newFilters); + setActiveFilterId(newFilter.key); + handleSphClose(); + }; + + // Chip handlers + const handleChipOpenChange = (isOpen: boolean) => { + if (!isOpen) setActiveFilterId(''); + }; + + const handleFilterChange = (filter: IFilterChipItem) => { + const newFilters = [...(drawerChipFilters ?? [])]; + const index = newFilters.findIndex((f) => f.key === filter.key); + if (index < 0) return; + + newFilters[index].operator = filter.operator ?? OperatorType.In; + newFilters[index].filters = filter.filters; + newFilters[index].isSelected = filter.isSelected; + newFilters[index].dateRange = filter.dateRange; + + const filterStartDate = filter.dateRange?.[0]; + const filterEndDate = filter.dateRange?.[1]; + + if (filterStartDate && filterEndDate && filterStartDate.isValid() && filterEndDate.isValid()) { + const normalizedStartDate = dayjs(filterStartDate.format(DATE_FORMAT)); + const normalizedEndDate = dayjs(filterEndDate.format(DATE_FORMAT)); + setDateRange([normalizedStartDate, normalizedEndDate]); + } + setDrawerChipFilters(newFilters); + }; + + const handleChipDelete = (chipId: React.Key) => { + const filterChipItems = drawerChipFilters?.filter((x) => x.key !== chipId && ChipKey[x.key] !== chipId); + if (filterChipItems) { + const dateFilter = newDateRangeChipItem([dateRange[0], dateRange[1]] as RangeValue); + if (filterChipItems.length === 0 || filterChipItems.findIndex((fci) => fci.key === ChipKey.DischargeDate) === -1) { + if (dateFilter) { + filterChipItems.push(...[dateFilter]); + } + } + setDrawerChipFilters(filterChipItems); + } + setActiveFilterId(''); + }; + + const handleChipClick = (chipId: React.Key) => { + const clickedChip = drawerChipFilters?.find((chip) => chip.key === chipId); + + // Prevent clicking on readOnly benchmark chips (prevents popover selection) + if (clickedChip?.isReadOnly && clickedChip.chipType === ChipType.sphCriteria) { + return; + } + + setActiveFilterId(chipId === activeFilterId ? '' : chipId); + }; + + // Exclusion criteria handlers + const handleALOSChange = (value: DropDownValue) => { + const selection = parseLosCostSelection(value.toString()); + if (drawerExclusionCriteria) { + setDrawerExclusionCriteria({ ...drawerExclusionCriteria, losExclusionType: selection.exclusionType, losExclusionValue: selection.value }); + } + }; + + const handleTDVCChange = (value: DropDownValue) => { + const selection = parseLosCostSelection(value.toString()); + if (drawerExclusionCriteria) { + setDrawerExclusionCriteria({ ...drawerExclusionCriteria, costExclusionType: selection.exclusionType, costExclusionValue: selection.value }); + } + }; + + const handleTopNFieldChange = (value: TopNField | null) => { + setDrawerExclusionCriteria({ ...drawerExclusionCriteria, topNField: value, topN: value === null ? null : drawerExclusionCriteria.topN }); + }; + + const handleTopNChange = (value: number | null) => { + setDrawerExclusionCriteria({ ...drawerExclusionCriteria, topN: value }); + }; + + // Prepare data for apply action + const getApplyPayload = () => { + // Save outlier values to session storage + setLosOutlierValue(parseLosTrimValue(drawerExclusionCriteria)); + setCostOutlierValue(parseCostTrimValue(drawerExclusionCriteria)); + + // Filter out readOnly benchmark chips - only return active filters + const activeFilters = (drawerChipFilters ?? []).filter((chip) => !chip.isReadOnly); + + return { + drawerExclusionCriteria, + activeFilters + }; + }; + + return { + // State + filters, + drawerChipFilters, + drawerExclusionCriteria, + activeFilterId, + + // Filter popover + isFilterPopoverVisible, + selectedKeys, + selectedNode, + handleOpenChange, + handleClose, + handleSelect, + handleAddFilter, + + // SPH popover + isSphPopoverVisible, + sphSelectedKeys, + sphSelectedNode, + sphCustomItems, + handleSphOpenChange, + handleSphClose, + handleSphSelect, + handleAddSphFilter, + + // Chips + handleChipOpenChange, + handleFilterChange, + handleChipDelete, + handleChipClick, + + // Exclusion criteria + handleALOSChange, + handleTDVCChange, + handleTopNFieldChange, + handleTopNChange, + + // Apply + getApplyPayload + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useScrollManagement.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useScrollManagement.ts new file mode 100644 index 0000000..c2550e3 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useScrollManagement.ts @@ -0,0 +1,118 @@ +import { RefObject, useCallback } from 'react'; + +interface IUseScrollManagementOptions { + /** Maximum number of attempts to find the element before giving up */ + maxAttempts?: number; + /** Delay between retry attempts in milliseconds */ + retryDelay?: number; + /** Initial delay before starting the scroll attempt in milliseconds */ + initialDelay?: number; +} + +interface IScrollToRowOptions { + /** The container ref that holds the TreeGrid */ + containerRef: RefObject; + /** The full row key for index-based fallback search */ + rowKey: string; + /** Array of data to use for index-based fallback */ + dataArray?: Array<{ key: string; data?: { name?: string } }>; +} + +/** + * Custom hook for managing smooth scrolling to TreeGrid rows. + * + * Provides a polling mechanism to wait for DOM updates and locate rows by + * searching for the row with the matching key in the current DOM. + * + * @param options - Configuration options for scroll behavior + * @returns scrollToRow - Function to initiate scrolling to a specific row + * + * @example + * const scrollToRow = useScrollManagement({ maxAttempts: 20, retryDelay: 50 }); + * + * // Later, in a click handler: + * scrollToRow({ + * containerRef: treeGridRef, + * rowKey: matchingRow.key, + * dataArray: encounterGroupSummaries + * }); + */ +export const useScrollManagement = (options: IUseScrollManagementOptions = {}) => { + const { maxAttempts = 20, retryDelay = 50, initialDelay = 100 } = options; + + /** + * Scrolls to a row in the TreeGrid using various search strategies. + * Uses a polling mechanism to wait for the element to appear in the DOM. + */ + const scrollToRow = useCallback( + (scrollOptions: IScrollToRowOptions) => { + const { containerRef, rowKey, dataArray } = scrollOptions; + + const attemptScroll = (attempts = 0) => { + if (attempts > maxAttempts) { + return; // Give up after max attempts + } + + let rowElement: Element | null | undefined = null; + + if (containerRef.current) { + // Fast-path: Try direct query using data-row-key attribute + rowElement = containerRef.current.querySelector(`[data-row-key="${rowKey}"]`); + } + + // Fallback: If direct query didn't find the row, try name/level scan + if (!rowElement && dataArray && containerRef.current) { + // Find the target row data + const targetRow = dataArray.find((row) => row.key === rowKey); + + if (targetRow) { + const targetName = targetRow.data?.name; + // Determine the expected tree level from the rowKey (e.g., "1-abc" = level 1) + const expectedLevel = rowKey.split('-')[0]; + + if (targetName) { + // Search all DOM rows for one with matching name at the correct level + const allRows = containerRef.current.querySelectorAll('tbody tr'); + + for (let i = 0; i < allRows.length; i++) { + const row = allRows[i]; + + // Check if this row is at the expected tree level + const rowLevel = row.getAttribute('aria-level') || row.getAttribute('data-level'); + + // Look for the name in the row + const nameCell = row.querySelector('span.p-treetable-node-label, td span') as HTMLElement; + const cellName = nameCell?.textContent?.trim(); + + // Match both the name and the tree level to ensure we get the right row + if (cellName === targetName && (!rowLevel || rowLevel === expectedLevel)) { + rowElement = row; + break; + } + } + } + } + } + + if (rowElement) { + rowElement.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'nearest' + }); + } else { + // Element not yet in DOM, try again + setTimeout(() => attemptScroll(attempts + 1), retryDelay); + } + }; + + // Start the scroll attempt after initial delay + setTimeout(() => attemptScroll(), initialDelay); + }, + [maxAttempts, retryDelay, initialDelay] + ); + + return scrollToRow; +}; + +export default useScrollManagement; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useTreeGridExpansion.ts b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useTreeGridExpansion.ts new file mode 100644 index 0000000..154e0e2 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/hooks/useTreeGridExpansion.ts @@ -0,0 +1,98 @@ +import { useCallback, useState } from 'react'; +import { IEncounterGroupDetailTreeGridData } from '../data/IEncounterGroupDetail'; + +/** + * Configuration options for the tree grid expansion hook. + */ +interface IUseTreeGridExpansionConfig { + /** The encounter group details data used to build expandable keys */ + encounterGroupDetails: IEncounterGroupDetailTreeGridData[] | undefined; +} + +/** + * Return value from the tree grid expansion hook. + */ +interface IUseTreeGridExpansionReturn { + /** Array of tree grid node keys that are currently expanded */ + treeGridKeysData: string[]; + /** Current expanded level (Entity, Code, Physician, or Encounter) */ + expandedLevel: TreeGridLevel; + /** Updates the expanded tree grid levels based on the specified position */ + updateExpandedLevels: (levelPos: TreeGridLevel) => void; +} + +/** + * Enumeration for tree grid levels corresponding to the hierarchy of the data. + */ +export enum TreeGridLevel { + Entity = 0, + Code = 1, + Physician = 2, + Encounter = 3 +} + +/** + * Custom hook for managing tree grid expansion state and operations. + * + * Handles the logic of expanding/collapsing tree grid nodes at different hierarchy levels, + * collecting all expandable keys, and filtering them based on the target expansion level. + * + * @param config - Configuration options for tree grid expansion + * @returns Object containing state and functions for tree grid expansion management + */ +export const useTreeGridExpansion = (config: IUseTreeGridExpansionConfig): IUseTreeGridExpansionReturn => { + const { encounterGroupDetails } = config; + + // ======================================================================== + // State + // ======================================================================== + + const [treeGridKeysData, setTreeGridKeysData] = useState([]); + const [expandedLevel, setExpandedLevel] = useState(TreeGridLevel.Entity); + + // ======================================================================== + // Callbacks + // ======================================================================== + + /** + * Updates the expanded tree grid levels based on the specified position. + * Collects all expandable keys from the tree structure and filters them to the target level. + * @param levelPos - The tree grid level to expand to + */ + const updateExpandedLevels = useCallback( + (levelPos: TreeGridLevel): void => { + const keys: string[] = []; + + /** + * Recursively collects all expandable keys from tree nodes. + * Keys are in the format "level-index" (e.g., "0-0", "1-2", "2-5"). + */ + function collectAllKeys(nodes: IEncounterGroupDetailTreeGridData[]): void { + nodes.forEach((node) => { + if (node.children && node.children.length) { + keys.push(node.key); + collectAllKeys(node.children); + } + }); + } + + collectAllKeys(encounterGroupDetails || []); + + // Filter keys to only include those at or below the target level + // The level is determined by the first number in the key (before the hyphen) + setTreeGridKeysData(keys.filter((key) => parseInt(key.split('-')[0], 10) <= levelPos)); + setExpandedLevel(levelPos); + }, + [encounterGroupDetails] + ); + + // ======================================================================== + // Return + // ======================================================================== + + return { + treeGridKeysData, + expandedLevel, + updateExpandedLevels + }; +}; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/opportunities/ExplorationDuplicatePopulationModal.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/opportunities/ExplorationDuplicatePopulationModal.tsx new file mode 100644 index 0000000..8b5f48f --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/opportunities/ExplorationDuplicatePopulationModal.tsx @@ -0,0 +1,115 @@ +import Button from '@strata/tempo/lib/button/Button'; +import Form from '@strata/tempo/lib/form/Form'; +import Input from '@strata/tempo/lib/input/Input'; +import Modal from '@strata/tempo/lib/modal/Modal'; +import Spacing from '@strata/tempo/lib/spacing/Spacing'; +import useToast from '@strata/tempo/lib/toast'; +import React, { useEffect } from 'react'; +import { RegEx } from '../../shared/validation/RegEx'; +import { populationService } from './data/populationService'; + +export interface IExplorationDuplicatePopulationModalProps { + isVisible: boolean; + populationId: number; + populationName: string; + closeModal: () => void; +} + +interface IExplorationDuplicatePopulationFormValues { + name?: string; +} + +const ExplorationDuplicatePopulationModal: React.FC = (props: IExplorationDuplicatePopulationModalProps) => { + const { isVisible, populationId, populationName, closeModal } = props; + const [loading, setLoading] = React.useState(false); + const [form] = Form.useForm(); + const toast = useToast(); + + useEffect(() => { + if (isVisible) { + form.setFieldsValue({ name: `${populationName} Copy` }); + } + }, [populationName, isVisible, form]); + + const handleFinish = async (values: IExplorationDuplicatePopulationFormValues) => { + try { + setLoading(true); + const result = await populationService.duplicatePopulation(populationId, values.name ?? ''); + if (result && result.explorationPopulationId) { + toast.show({ + key: `Exploration Population - ${result.explorationPopulationId}`, + toastType: 'success', + message: 'Exploration Population duplicated.' + }); + window.location.assign(`/continuousimprovement/exploration-populations/${result.explorationPopulationId}`); + closeModal(); + } else { + toast.show({ + key: `Exploration Population - ${populationId}`, + toastType: 'error', + message: 'Exploration Population was not duplicated. Try again and contact us if the issue continues.' + }); + } + } catch { + toast.show({ + key: `Exploration Population - ${populationId}`, + toastType: 'error', + message: `Unable to duplicate the Exploration Population.`, + duration: 0 + }); + } finally { + setLoading(false); + } + }; + + const handleCloseDuplicateModal = () => { + form.resetFields(['name']); + closeModal(); + }; + + return ( + + +
+ ({ + async validator(_, value) { + if (!value) { + return Promise.resolve(); + } + try { + const isNameUnique = await populationService.isNameUnique(value); + if (!isNameUnique) { + return Promise.reject('Exploration population with that name already exists'); + } + return Promise.resolve(); + } catch { + return Promise.reject('Unable to validate name uniqueness. Please try again.'); + } + } + }) + ]} + > + + + + + + +
+
+
+ ); +}; + +export default ExplorationDuplicatePopulationModal; diff --git a/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/opportunities/ExplorationOpportunityDetail.tsx b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/opportunities/ExplorationOpportunityDetail.tsx new file mode 100644 index 0000000..8899637 --- /dev/null +++ b/src/Strata.ContinuousImprovement.Web/continuousimprovement/src/exploration/opportunities/ExplorationOpportunityDetail.tsx @@ -0,0 +1,177 @@ +import { useModal } from '@strata/tempo/lib'; +import Button from '@strata/tempo/lib/button'; +import ButtonMenu from '@strata/tempo/lib/buttonmenu/ButtonMenu'; +import Divider from '@strata/tempo/lib/divider'; +import DropDown, { DropDownValue } from '@strata/tempo/lib/dropdown'; +import Header from '@strata/tempo/lib/header'; +import BorderIcon from '@strata/tempo/lib/icon/BorderIcon'; +import CheckIcon from '@strata/tempo/lib/icon/CheckIcon'; +import EditIcon from '@strata/tempo/lib/icon/EditIcon'; +import MetricGroup, { IMetricItem } from '@strata/tempo/lib/metricgroup'; +import Spacing from '@strata/tempo/lib/spacing'; +import Text from '@strata/tempo/lib/text'; +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router'; +import { OpportunityTypes } from '../../opportunities/opportunities/data/IAllOpportunity'; +import { analyticsService } from '../../shared/analytics/data/analyticsService'; +import CommentsDrawer from '../../shared/comments/CommentsDrawer'; +import { IInitiativeStatuses } from '../../shared/data/IInitiativeStatuses'; +import ExplorationOpportunityModal from './ExplorationOpportunityModal'; +import { IExplorationOpportunity } from './data/IExplorationOpportunity'; +import { opportunityService } from './data/opportunityservice'; + +const ExplorationOpportunityDetail: React.FC = () => { + const { opportunityGuid } = useParams<{ opportunityGuid: string }>(); + const [opportunityId, setOpportunityId] = useState(0); + const [modalVisible, setModalVisible] = useState(false); + const [opportunityDetailData, setOpportunityDetailData] = useState(); + const modal = useModal(); + const [showComments, setShowComments] = useState(false); + const [presentationMode, setPresentationMode] = useState(false); + + useEffect(() => { + const fetchData = async () => { + await opportunityService.checkWorkbook(opportunityGuid); + const opportunityDetail: IExplorationOpportunity = await opportunityService.getById(opportunityGuid); + setOpportunityId(opportunityDetail.opportunityId); + setOpportunityDetailData(opportunityDetail); + }; + fetchData(); + }, [opportunityGuid, modalVisible]); + + const metrics: IMetricItem[] = [ + { + key: 'identified-savings', + label: 'IDENTIFIED VARIATION', + values: [ + { + key: 'identifiedsavings', + label: 'Identified Variation', + value: opportunityDetailData?.identifiedSavings, + format: 'currency' + } + ] + } + ]; + + const handleDelete = async () => { + if (!opportunityDetailData) { + return; + } + modal.confirm({ + title: `Permanently delete opportunity ${opportunityDetailData.name}?`, + content: `This will permanently delete the opportunity ${opportunityDetailData.name}.`, + okText: 'Delete', + async onOk() { + try { + const workbookId = opportunityDetailData.analyticsWorkbookId; + if (workbookId !== 0) { + await analyticsService.deleteWorkbook(workbookId); + } + } finally { + await opportunityService.delete(opportunityGuid); + window.location.replace(`/continuousimprovement/opportunities`); + } + } + }); + }; + + const addComment = async () => { + setShowComments(true); + }; + + const headerExtra = ( + <> + + + + + ); + + return ( + <> +
+ {!presentationMode && ( + <> + + + + + + + + + + + + + STATUS + + + { + const opp = await opportunityService.setStatus(opportunityGuid, value); + setOpportunityDetailData(opp); + }} + items={[ + { text: 'On Hold', value: IInitiativeStatuses[IInitiativeStatuses.OnHold] }, + { text: 'In Process', value: IInitiativeStatuses[IInitiativeStatuses.InProcess] }, + { text: 'Completed', value: IInitiativeStatuses[IInitiativeStatuses.Completed] } + ]} + /> + + + + + + + )} + {opportunityDetailData?.analyticsWorkbookId !== 0 && ( + +