Skip to content

Commit be468fd

Browse files
Merge pull request #642 from akvo/feature/639-new-procurement-library-oct-2025
Feature/639 new procurement library oct 2025
2 parents 9d6c1e9 + 79bdc4b commit be468fd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+7406
-291
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
"""create new procurement library updates oct 2025
2+
3+
Revision ID: 92819753edfb
4+
Revises: 7d088afac59f
5+
Create Date: 2025-10-22 01:01:07.013270
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = '92819753edfb'
16+
down_revision: Union[str, None] = '7d088afac59f'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
# === pl_category ===
23+
op.create_table(
24+
"pl_category",
25+
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
26+
sa.Column("name", sa.String(length=125), nullable=False),
27+
sa.Column("description", sa.Text(), nullable=True),
28+
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
29+
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()),
30+
)
31+
op.create_index(op.f("ix_pl_category_id"), "pl_category", ["id"], unique=True)
32+
op.create_index(op.f("ix_pl_category_name"), "pl_category", ["name"], unique=False)
33+
34+
# === pl_attribute ===
35+
op.create_table(
36+
"pl_attribute",
37+
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
38+
sa.Column(
39+
"category_id",
40+
sa.Integer(),
41+
sa.ForeignKey("pl_category.id", ondelete="SET NULL"),
42+
nullable=True,
43+
),
44+
sa.Column("label", sa.String(length=125), nullable=False),
45+
sa.Column("description", sa.Text(), nullable=True),
46+
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
47+
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()),
48+
)
49+
op.create_index(op.f("ix_pl_attribute_id"), "pl_attribute", ["id"], unique=True)
50+
op.create_index(op.f("ix_pl_attribute_label"), "pl_attribute", ["label"], unique=False)
51+
op.create_index(op.f("ix_pl_attribute_category_id"), "pl_attribute", ["category_id"], unique=False)
52+
53+
# === pl_practice_intervention ===
54+
op.create_table(
55+
"pl_practice_intervention",
56+
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
57+
sa.Column("label", sa.String(length=225), nullable=False),
58+
sa.Column("intervention_definition", sa.Text(), nullable=True),
59+
sa.Column("enabling_conditions", sa.Text(), nullable=True),
60+
sa.Column("business_rationale", sa.Text(), nullable=True),
61+
sa.Column("farmer_rationale", sa.Text(), nullable=True),
62+
sa.Column("risks_n_trade_offs", sa.Text(), nullable=True),
63+
sa.Column("intervention_impact_income", sa.Text(), nullable=True),
64+
sa.Column("intervention_impact_env", sa.Text(), nullable=True),
65+
sa.Column("source_or_evidence", sa.Text(), nullable=True),
66+
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
67+
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()),
68+
)
69+
op.create_index(op.f("ix_pl_practice_intervention_id"), "pl_practice_intervention", ["id"], unique=True)
70+
op.create_index(op.f("ix_pl_practice_intervention_label"), "pl_practice_intervention", ["label"], unique=False)
71+
72+
# === pl_practice_intervention_tag ===
73+
op.create_table(
74+
"pl_practice_intervention_tag",
75+
sa.Column(
76+
"practice_intervention_id",
77+
sa.Integer(),
78+
sa.ForeignKey("pl_practice_intervention.id", ondelete="CASCADE"),
79+
primary_key=True,
80+
),
81+
sa.Column(
82+
"attribute_id",
83+
sa.Integer(),
84+
sa.ForeignKey("pl_attribute.id", ondelete="CASCADE"),
85+
primary_key=True,
86+
),
87+
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
88+
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()),
89+
)
90+
op.create_index(op.f("ix_pl_practice_intervention_tag_practice_intervention_id"), "pl_practice_intervention_tag", ["practice_intervention_id"], unique=False)
91+
op.create_index(op.f("ix_pl_practice_intervention_tag_attribute_id"), "pl_practice_intervention_tag", ["attribute_id"], unique=False)
92+
93+
# === pl_indicator ===
94+
op.create_table(
95+
"pl_indicator",
96+
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
97+
sa.Column("name", sa.String(length=50), nullable=False),
98+
sa.Column("label", sa.String(length=125), nullable=False),
99+
sa.Column("description", sa.Text(), nullable=True),
100+
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
101+
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()),
102+
)
103+
op.create_index(op.f("ix_pl_indicator_id"), "pl_indicator", ["id"], unique=True)
104+
op.create_index(op.f("ix_pl_indicator_name"), "pl_indicator", ["name"], unique=True)
105+
op.create_index(op.f("ix_pl_indicator_label"), "pl_indicator", ["label"], unique=False)
106+
107+
# === pl_practice_intervention_indicator_score ===
108+
op.create_table(
109+
"pl_practice_intervention_indicator_score",
110+
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
111+
sa.Column(
112+
"practice_intervention_id",
113+
sa.Integer(),
114+
sa.ForeignKey("pl_practice_intervention.id", ondelete="CASCADE"),
115+
nullable=False,
116+
),
117+
sa.Column(
118+
"indicator_id",
119+
sa.Integer(),
120+
sa.ForeignKey("pl_indicator.id", ondelete="CASCADE"),
121+
nullable=False,
122+
),
123+
sa.Column("score", sa.Float(), nullable=True),
124+
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
125+
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now(), onupdate=sa.func.now()),
126+
)
127+
op.create_index(op.f("ix_pl_practice_intervention_indicator_score_id"), "pl_practice_intervention_indicator_score", ["id"], unique=True)
128+
op.create_index(op.f("ix_pl_practice_intervention_indicator_score_practice_intervention_id"), "pl_practice_intervention_indicator_score", ["practice_intervention_id"], unique=False)
129+
op.create_index(op.f("ix_pl_practice_intervention_indicator_score_indicator_id"), "pl_practice_intervention_indicator_score", ["indicator_id"], unique=False)
130+
op.create_unique_constraint(
131+
"uq_pl_practice_intervention_indicator_score_practice_indicator",
132+
"pl_practice_intervention_indicator_score",
133+
["practice_intervention_id", "indicator_id"],
134+
)
135+
136+
137+
def downgrade() -> None:
138+
op.drop_constraint("uq_pl_practice_intervention_indicator_score_practice_indicator", "pl_practice_intervention_indicator_score", type_="unique")
139+
op.drop_index(op.f("ix_pl_practice_intervention_indicator_score_id"), table_name="pl_practice_intervention_indicator_score")
140+
op.drop_index(op.f("ix_pl_practice_intervention_indicator_score_practice_intervention_id"), table_name="pl_practice_intervention_indicator_score")
141+
op.drop_index(op.f("ix_pl_practice_intervention_indicator_score_indicator_id"), table_name="pl_practice_intervention_indicator_score")
142+
op.drop_table("pl_practice_intervention_indicator_score")
143+
144+
op.drop_index(op.f("ix_pl_indicator_id"), table_name="pl_indicator")
145+
op.drop_index(op.f("ix_pl_indicator_name"), table_name="pl_indicator")
146+
op.drop_index(op.f("ix_pl_indicator_label"), table_name="pl_indicator")
147+
op.drop_table("pl_indicator")
148+
149+
op.drop_index(op.f("ix_pl_practice_intervention_tag_practice_intervention_id"), table_name="pl_practice_intervention_tag")
150+
op.drop_index(op.f("ix_pl_practice_intervention_tag_attribute_id"), table_name="pl_practice_intervention_tag")
151+
op.drop_table("pl_practice_intervention_tag")
152+
153+
op.drop_index(op.f("ix_pl_practice_intervention_id"), table_name="pl_practice_intervention")
154+
op.drop_index(op.f("ix_pl_practice_intervention_label"), table_name="pl_practice_intervention")
155+
op.drop_table("pl_practice_intervention")
156+
157+
op.drop_index(op.f("ix_pl_attribute_id"), table_name="pl_attribute")
158+
op.drop_index(op.f("ix_pl_attribute_label"), table_name="pl_attribute")
159+
op.drop_index(op.f("ix_pl_attribute_category_id"), table_name="pl_attribute")
160+
op.drop_table("pl_attribute")
161+
162+
op.drop_index(op.f("ix_pl_category_id"), table_name="pl_category")
163+
op.drop_index(op.f("ix_pl_category_name"), table_name="pl_category")
164+
op.drop_table("pl_category")

backend/core/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
from routes.procurement_library.procurement_process import (
2727
procurement_process_route,
2828
)
29+
from routes.procurement_library_v2.practice import pl_practice_router_v2
30+
from routes.procurement_library_v2.category import pl_cat_router_v2
2931

3032
import os
3133
from jsmin import jsmin
@@ -140,6 +142,8 @@ def generate_config_file() -> None:
140142
app.include_router(assessment_question_route)
141143
app.include_router(practice_route)
142144
app.include_router(procurement_process_route)
145+
app.include_router(pl_cat_router_v2)
146+
app.include_router(pl_practice_router_v2)
143147

144148

145149
@app.get("/", tags=["Dev"])

backend/db/procurement_library_v2/__init__.py

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from sqlalchemy.orm import Session, joinedload
2+
from typing import List, Dict
3+
4+
from models.procurement_library_v2.pl_models import PLCategory
5+
6+
7+
def get_categories_with_attributes(db: Session) -> List[Dict]:
8+
"""Return all categories with their attributes using ORM query."""
9+
categories = (
10+
db.query(PLCategory)
11+
.options(joinedload(PLCategory.attributes))
12+
.order_by(PLCategory.id)
13+
.all()
14+
)
15+
16+
return [
17+
cat.category_with_attributes for cat in categories
18+
]

0 commit comments

Comments
 (0)