Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python module renamed. Tag workflow added. #34

Merged
merged 2 commits into from
Jan 6, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .copier-answers.yml
Original file line number Diff line number Diff line change
@@ -7,10 +7,10 @@ github_ci_precommit: true
github_ci_pypi_publish: true
github_ci_tests: true
github_ci_tests_codecov: true
module_name: openg2p_portal_api
module_name: openg2p_beneficiary_portal_api
org_name: OpenG2P
org_slug: OpenG2P
package_name: openg2p-portal-api
repo_name: OpenG2P Portal API
repo_slug: openg2p-portal-api
package_name: openg2p-beneficiary-portal-api
repo_name: OpenG2P Beneficiary Portal API
repo_slug: openg2p-beneficiary-portal-api

2 changes: 1 addition & 1 deletion .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
env:
NAMESPACE: ${{ secrets.docker_hub_organisation || 'openg2p' }}
SERVICE_NAME: openg2p-selfservice-api
SERVICE_NAME: openg2p-beneficiary-portal-api
steps:
- uses: actions/checkout@v3
- name: Docker build
3 changes: 1 addition & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: Publish to PyPI

on:
release:
types: [published]
workflow_dispatch:

jobs:
publish-to-pypi:
21 changes: 21 additions & 0 deletions .github/workflows/tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Tag the repo
on:
workflow_dispatch:
inputs:
new-tag:
description: Tag in "vN.N.N" format
required: true
type: string
previous-tag:
description: Previous tag. "None" if no previous tag
required: true
type: string
default: latest
jobs:
tag-repo:
uses: openg2p/openg2p-packaging/.github/workflows/tag.yml@main
with:
new-tag: ${{ inputs.new-tag }}
previous-tag: ${{ inputs.previous-tag }}
secrets:
OPENG2P_BOT_GITHUB_PAT: ${{ secrets.OPENG2P_BOT_GITHUB_PAT }}
11 changes: 1 addition & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -20,15 +20,6 @@ jobs:
- "3.10"
os:
- ubuntu-latest
services:
postgres:
image: postgres:9.6
env:
POSTGRES_USER: openg2p
POSTGRES_PASSWORD: openg2p
POSTGRES_DB: openg2p
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
with:
@@ -43,7 +34,7 @@ jobs:
python -m pip install -e .
- name: Run test suite
run: |
pytest --cov-branch --cov-report=term-missing --cov=openg2p_portal_api --cov=tests
pytest --cov-branch --cov-report=term-missing --cov=openg2p_beneficiary_portal_api --cov=tests
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
2 changes: 0 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -19,8 +19,6 @@ USER ${container_user}
ADD --chown=${container_user}:${container_user_group} . /app/src
ADD --chown=${container_user}:${container_user_group} main.py /app

RUN python3 -m venv venv \
&& . ./venv/bin/activate
RUN python3 -m pip install \
git+https://github.com/openg2p/openg2p-fastapi-common@develop\#subdirectory=openg2p-fastapi-common \
git+https://github.com/openg2p/openg2p-fastapi-common@develop\#subdirectory=openg2p-fastapi-auth \
15 changes: 7 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# OpenG2P Portal API
[![Pre-commit Status](https://github.com/OpenG2P/openg2p-portal-api/actions/workflows/pre-commit.yml/badge.svg?branch=develop)](https://github.com/OpenG2P/openg2p-portal-api/actions/workflows/pre-commit.yml?query=branch%3Adevelop)
[![Build Status](https://github.com/OpenG2P/openg2p-portal-api/actions/workflows/test.yml/badge.svg?branch=develop)](https://github.com/OpenG2P/openg2p-portal-api/actions/workflows/test.yml?query=branch%3Adevelop)
[![codecov](https://codecov.io/gh/OpenG2P/openg2p-portal-api/branch/develop/graph/badge.svg)](https://codecov.io/gh/OpenG2P/openg2p-portal-api)
[![openapi](https://img.shields.io/badge/open--API-swagger-brightgreen)](https://validator.swagger.io/?url=https://raw.githubusercontent.com/OpenG2P/openg2p-portal-api/develop/api-docs/generated/openapi.json)
![PyPI](https://img.shields.io/pypi/v/openg2p-portal-api?label=pypi%20package)
![PyPI - Downloads](https://img.shields.io/pypi/dm/openg2p-portal-api)

# OpenG2P Beneficiary Portal API
[![Pre-commit Status](https://github.com/OpenG2P/openg2p-beneficiary-portal-api/actions/workflows/pre-commit.yml/badge.svg?branch=develop)](https://github.com/OpenG2P/openg2p-beneficiary-portal-api/actions/workflows/pre-commit.yml?query=branch%3Adevelop)
[![Build Status](https://github.com/OpenG2P/openg2p-beneficiary-portal-api/actions/workflows/test.yml/badge.svg?branch=develop)](https://github.com/OpenG2P/openg2p-beneficiary-portal-api/actions/workflows/test.yml?query=branch%3Adevelop)
[![codecov](https://codecov.io/gh/OpenG2P/openg2p-beneficiary-portal-api/branch/develop/graph/badge.svg)](https://codecov.io/gh/OpenG2P/openg2p-beneficiary-portal-api)
[![openapi](https://img.shields.io/badge/open--API-swagger-brightgreen)](https://validator.swagger.io/?url=https://raw.githubusercontent.com/OpenG2P/openg2p-beneficiary-portal-api/develop/api-docs/generated/openapi.json)
[![PyPI](https://img.shields.io/pypi/v/openg2p-beneficiary-portal-api?label=pypi%20package)](https://pypi.org/project/openg2p-beneficiary-portal-api)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/openg2p-beneficiary-portal-api)](https://pypi.org/project/openg2p-beneficiary-portal-api)


## Licenses
4 changes: 3 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,9 @@

# ruff: noqa: I001

from openg2p_portal_api.app import Initializer as SelfServicePortalInitializer
from openg2p_beneficiary_portal_api.app import (
Initializer as SelfServicePortalInitializer,
)
from openg2p_fastapi_common.ping import PingInitializer

main_init = SelfServicePortalInitializer()
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "openg2p-portal-api"
name = "openg2p-beneficiary-portal-api"
authors = [
{ name="OpenG2P", email="[email protected]" },
]
@@ -26,8 +26,8 @@ dynamic = ["version"]
[project.urls]
Homepage = "https://openg2p.org"
Documentation = "https://docs.openg2p.org/"
Repository = "https://github.com/OpenG2P/openg2p-portal-api"
Source = "https://github.com/OpenG2P/openg2p-portal-api"
Repository = "https://github.com/OpenG2P/openg2p-beneficiary-portal-api"
Source = "https://github.com/OpenG2P/openg2p-beneficiary-portal-api"

[tool.hatch.version]
path = "src/openg2p_portal_api/__init__.py"
path = "src/openg2p_beneficiary_portal_api/__init__.py"
1 change: 1 addition & 0 deletions src/openg2p_beneficiary_portal_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.0.0"
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -13,9 +13,9 @@ class Settings(AuthSettings, Settings):
env_prefix="portal_", env_file=".env", extra="allow"
)

openapi_title: str = "G2P Portal API"
openapi_title: str = "G2P Beneficiary Portal API"
openapi_description: str = """
This module implements G2P Portal APIs.
This module implements G2P Beneficiary Portal APIs.

***********************************
Further details goes here
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -7,11 +7,10 @@
UnauthorizedError,
)

from openg2p_portal_api.models.document_file import DocumentFile

from ..config import Settings
from ..dependencies import JwtBearerAuth
from ..models.credentials import AuthCredentials
from ..models.document_file import DocumentFile
from ..services.document_file_service import DocumentFileService

_config = Settings.get_config()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
from sqlalchemy import JSON, Boolean, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship

from openg2p_portal_api.models.orm.document_file_orm import DocumentFileORM
from .document_file_orm import DocumentFileORM


class DocumentStoreORM(BaseORMModel):
Original file line number Diff line number Diff line change
@@ -15,8 +15,7 @@
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.orm import Mapped, mapped_column, relationship

from openg2p_portal_api.models.orm.document_file_orm import DocumentFileORM

from .document_file_orm import DocumentFileORM
from .reg_id_orm import RegIDORM


Original file line number Diff line number Diff line change
@@ -7,8 +7,7 @@
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.orm import Mapped, mapped_column, relationship

from openg2p_portal_api.models.orm.document_file_orm import DocumentFileORM

from .document_file_orm import DocumentFileORM
from .program_registrant_info_orm import ProgramRegistrantInfoORM


File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -12,10 +12,10 @@
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import async_sessionmaker

from openg2p_portal_api.exception import handle_exception
from openg2p_portal_api.models.document_file import DocumentFile
from openg2p_portal_api.services.membership_service import MembershipService
from openg2p_portal_api.utils.file_utils import (
from ..exception import handle_exception
from ..models.document_file import DocumentFile
from ..models.orm.document_file_orm import DocumentFileORM
from ..utils.file_utils import (
compute_human_file_size,
create_or_update_tag,
extract_filename,
@@ -24,8 +24,7 @@
get_s3_backend_config,
update_slug_relative_path,
)

from ..models.orm.document_file_orm import DocumentFileORM
from .membership_service import MembershipService


class DocumentFileService(BaseService):
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -6,11 +6,11 @@
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError

from openg2p_portal_api.exception import handle_exception
from openg2p_portal_api.models.orm.document_file_orm import DocumentFileORM
from openg2p_portal_api.models.orm.document_store_orm import DocumentStoreORM
from openg2p_portal_api.models.orm.document_tag_orm import DocumentTagORM
from openg2p_portal_api.models.orm.program_orm import ProgramORM
from ..exception import handle_exception
from ..models.orm.document_file_orm import DocumentFileORM
from ..models.orm.document_store_orm import DocumentStoreORM
from ..models.orm.document_tag_orm import DocumentTagORM
from ..models.orm.program_orm import ProgramORM

# The methods below enable concurrent document uploads to Odoo and MinIO (S3-compatible).
# - get_s3_backend_config
1 change: 0 additions & 1 deletion src/openg2p_portal_api/__init__.py

This file was deleted.

1 change: 0 additions & 1 deletion test-requirements.txt
Original file line number Diff line number Diff line change
@@ -2,6 +2,5 @@ pytest-cov
pytest-asyncio
pytest
pytest-mock
python-slugify>=8.0.0
git+https://github.com/openg2p/openg2p-fastapi-common@develop#subdirectory=openg2p-fastapi-common
git+https://github.com/openg2p/openg2p-fastapi-common@develop#subdirectory=openg2p-fastapi-auth
14 changes: 8 additions & 6 deletions tests/test_auth_controller.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from openg2p_fastapi_auth.models.orm.login_provider import LoginProvider
from openg2p_portal_api.controllers.auth_controller import AuthController
from openg2p_portal_api.models.orm.auth_oauth_provider import AuthOauthProviderORM
from openg2p_portal_api.models.orm.partner_orm import (
from openg2p_beneficiary_portal_api.controllers.auth_controller import AuthController
from openg2p_beneficiary_portal_api.models.orm.auth_oauth_provider import (
AuthOauthProviderORM,
)
from openg2p_beneficiary_portal_api.models.orm.partner_orm import (
BankORM,
PartnerBankORM,
PartnerORM,
PartnerPhoneNoORM,
)
from openg2p_portal_api.models.orm.reg_id_orm import RegIDORM, RegIDTypeORM
from openg2p_portal_api.models.profile import UpdateProfile
from openg2p_beneficiary_portal_api.models.orm.reg_id_orm import RegIDORM, RegIDTypeORM
from openg2p_beneficiary_portal_api.models.profile import UpdateProfile
from openg2p_fastapi_auth.models.orm.login_provider import LoginProvider
from sqlalchemy.exc import IntegrityError

TEST_CONSTANTS = {
10 changes: 6 additions & 4 deletions tests/test_discovery_controller.py
Original file line number Diff line number Diff line change
@@ -2,9 +2,11 @@
from unittest.mock import AsyncMock, patch

import pytest
from openg2p_portal_api.controllers.discovery_controller import DiscoveryController
from openg2p_portal_api.models.program import ProgramBase
from openg2p_portal_api.services.program_service import ProgramService
from openg2p_beneficiary_portal_api.controllers.discovery_controller import (
DiscoveryController,
)
from openg2p_beneficiary_portal_api.models.program import ProgramBase
from openg2p_beneficiary_portal_api.services.program_service import ProgramService

TEST_DATA = {
"PROGRAM_NAME": "Test Program",
@@ -23,7 +25,7 @@ def mock_program_service(self):
@pytest.fixture
def discovery_controller(self, mock_program_service):
with patch(
"openg2p_portal_api.services.program_service.ProgramService.get_component",
"openg2p_beneficiary_portal_api.services.program_service.ProgramService.get_component",
return_value=mock_program_service,
):
controller = DiscoveryController()
8 changes: 4 additions & 4 deletions tests/test_document_file_controller.py
Original file line number Diff line number Diff line change
@@ -2,15 +2,15 @@

import pytest
from fastapi import UploadFile
from openg2p_beneficiary_portal_api.controllers.document_file_controller import (
DocumentFileController,
)
from openg2p_beneficiary_portal_api.models.document_file import DocumentFile
from openg2p_fastapi_auth.models.credentials import AuthCredentials
from openg2p_fastapi_common.errors.http_exceptions import (
BadRequestError,
UnauthorizedError,
)
from openg2p_portal_api.controllers.document_file_controller import (
DocumentFileController,
)
from openg2p_portal_api.models.document_file import DocumentFile


@pytest.fixture
16 changes: 10 additions & 6 deletions tests/test_document_file_service.py
Original file line number Diff line number Diff line change
@@ -2,13 +2,17 @@

import pytest
from fastapi import UploadFile
from openg2p_beneficiary_portal_api.models.document_file import DocumentFile
from openg2p_beneficiary_portal_api.models.orm.document_file_orm import DocumentFileORM
from openg2p_beneficiary_portal_api.models.orm.document_store_orm import (
DocumentStoreORM,
)
from openg2p_beneficiary_portal_api.models.orm.document_tag_orm import DocumentTagORM
from openg2p_beneficiary_portal_api.models.orm.program_orm import ProgramORM
from openg2p_beneficiary_portal_api.services.document_file_service import (
DocumentFileService,
)
from openg2p_fastapi_common.errors.http_exceptions import BadRequestError
from openg2p_portal_api.models.document_file import DocumentFile
from openg2p_portal_api.models.orm.document_file_orm import DocumentFileORM
from openg2p_portal_api.models.orm.document_store_orm import DocumentStoreORM
from openg2p_portal_api.models.orm.document_tag_orm import DocumentTagORM
from openg2p_portal_api.models.orm.program_orm import ProgramORM
from openg2p_portal_api.services.document_file_service import DocumentFileService
from sqlalchemy.ext.asyncio import AsyncSession

TEST_CONSTANTS = {
2 changes: 1 addition & 1 deletion tests/test_exception.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest
from openg2p_beneficiary_portal_api.exception import handle_exception
from openg2p_fastapi_common.errors.http_exceptions import BadRequestError
from openg2p_portal_api.exception import handle_exception


class TestExceptionHandler:
11 changes: 7 additions & 4 deletions tests/test_form_controller.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from unittest.mock import AsyncMock, MagicMock

import pytest
from openg2p_beneficiary_portal_api.controllers.form_controller import FormController
from openg2p_beneficiary_portal_api.models.credentials import AuthCredentials
from openg2p_beneficiary_portal_api.models.form import (
ProgramForm,
ProgramRegistrantInfo,
)
from openg2p_beneficiary_portal_api.models.program import Program
from openg2p_fastapi_common.errors.http_exceptions import (
BadRequestError,
UnauthorizedError,
)
from openg2p_portal_api.controllers.form_controller import FormController
from openg2p_portal_api.models.credentials import AuthCredentials
from openg2p_portal_api.models.form import ProgramForm, ProgramRegistrantInfo
from openg2p_portal_api.models.program import Program


@pytest.fixture
8 changes: 4 additions & 4 deletions tests/test_form_service.py
Original file line number Diff line number Diff line change
@@ -3,12 +3,12 @@

import pytest
from fastapi import HTTPException
from openg2p_portal_api.models.form import ProgramForm
from openg2p_portal_api.models.orm.program_orm import ProgramORM
from openg2p_portal_api.models.orm.program_registrant_info_orm import (
from openg2p_beneficiary_portal_api.models.form import ProgramForm
from openg2p_beneficiary_portal_api.models.orm.program_orm import ProgramORM
from openg2p_beneficiary_portal_api.models.orm.program_registrant_info_orm import (
ProgramRegistrantInfoDraftORM,
)
from openg2p_portal_api.services.form_service import FormService
from openg2p_beneficiary_portal_api.services.form_service import FormService

TEST_DATA = {
"PROGRAM_ID": 1,
8 changes: 5 additions & 3 deletions tests/test_membership_service.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from openg2p_portal_api.models.orm.program_membership_orm import ProgramMembershipORM
from openg2p_portal_api.services.membership_service import MembershipService
from openg2p_beneficiary_portal_api.models.orm.program_membership_orm import (
ProgramMembershipORM,
)
from openg2p_beneficiary_portal_api.services.membership_service import MembershipService
from sqlalchemy.exc import IntegrityError


@@ -19,7 +21,7 @@ def mock_session():
def mock_session_maker(mock_session):
session, async_session = mock_session
with patch(
"openg2p_portal_api.services.membership_service.async_sessionmaker",
"openg2p_beneficiary_portal_api.services.membership_service.async_sessionmaker",
return_value=lambda: async_session,
) as session_maker:
yield session_maker
6 changes: 4 additions & 2 deletions tests/test_oauth_controller.py
Original file line number Diff line number Diff line change
@@ -4,10 +4,12 @@
import httpx
import pytest
from fastapi import Request
from openg2p_beneficiary_portal_api.controllers.oauth_controller import OAuthController
from openg2p_beneficiary_portal_api.models.orm.auth_oauth_provider import (
AuthOauthProviderORM,
)
from openg2p_fastapi_auth.models.orm.login_provider import LoginProviderTypes
from openg2p_fastapi_common.utils import cookie_utils
from openg2p_portal_api.controllers.oauth_controller import OAuthController
from openg2p_portal_api.models.orm.auth_oauth_provider import AuthOauthProviderORM

TEST_CONSTANTS = {
"CLIENT_ID": "test_client_id",
12 changes: 7 additions & 5 deletions tests/test_partner_service.py
Original file line number Diff line number Diff line change
@@ -2,9 +2,9 @@
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from openg2p_beneficiary_portal_api.models.orm.partner_orm import PartnerORM
from openg2p_beneficiary_portal_api.services.partner_service import PartnerService
from openg2p_fastapi_common.errors.http_exceptions import InternalServerError
from openg2p_portal_api.models.orm.partner_orm import PartnerORM
from openg2p_portal_api.services.partner_service import PartnerService
from sqlalchemy import Engine
from sqlalchemy.ext.asyncio import AsyncSession

@@ -58,7 +58,9 @@ def mock_session(mock_engine):

@pytest.fixture
def partner_service(mock_session, mock_engine):
with patch("openg2p_portal_api.services.partner_service.dbengine") as mock_dbengine:
with patch(
"openg2p_beneficiary_portal_api.services.partner_service.dbengine"
) as mock_dbengine:
mock_dbengine.get.return_value = mock_engine
return PartnerService()

@@ -71,7 +73,7 @@ async def test_check_and_create_partner_success(
expected_fields = ["name", "email", "phone", "gender", "birthdate"]

with patch(
"openg2p_portal_api.models.orm.reg_id_orm.RegIDORM.get_partner_by_reg_id",
"openg2p_beneficiary_portal_api.models.orm.reg_id_orm.RegIDORM.get_partner_by_reg_id",
new_callable=AsyncMock,
return_value=None,
), patch.object(
@@ -80,7 +82,7 @@ async def test_check_and_create_partner_success(
new_callable=AsyncMock,
return_value=expected_fields,
), patch(
"openg2p_portal_api.services.partner_service.async_sessionmaker",
"openg2p_beneficiary_portal_api.services.partner_service.async_sessionmaker",
return_value=lambda: mock_session[1],
):
await partner_service.check_and_create_partner(
10 changes: 6 additions & 4 deletions tests/test_program_controller.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from unittest.mock import AsyncMock, MagicMock

import pytest
from openg2p_portal_api.controllers.program_controller import ProgramController
from openg2p_portal_api.models.credentials import AuthCredentials
from openg2p_portal_api.models.program import (
from openg2p_beneficiary_portal_api.controllers.program_controller import (
ProgramController,
)
from openg2p_beneficiary_portal_api.models.credentials import AuthCredentials
from openg2p_beneficiary_portal_api.models.program import (
ApplicationDetails,
BenefitDetails,
Program,
ProgramSummary,
)
from openg2p_portal_api.services.program_service import ProgramService
from openg2p_beneficiary_portal_api.services.program_service import ProgramService

TEST_DATA = {
"PARTNER_ID": 1,
6 changes: 3 additions & 3 deletions tests/test_program_service.py
Original file line number Diff line number Diff line change
@@ -2,11 +2,11 @@
from unittest.mock import MagicMock

import pytest
from openg2p_portal_api.models.orm.program_orm import ProgramORM
from openg2p_portal_api.models.orm.program_registrant_info_orm import (
from openg2p_beneficiary_portal_api.models.orm.program_orm import ProgramORM
from openg2p_beneficiary_portal_api.models.orm.program_registrant_info_orm import (
ProgramRegistrantInfoORM,
)
from openg2p_portal_api.services.program_service import ProgramService
from openg2p_beneficiary_portal_api.services.program_service import ProgramService

TEST_DATA = {
"PROGRAM": {