diff --git a/spp b/spp
index f65088bd..fb6e086b 100755
--- a/spp
+++ b/spp
@@ -22,7 +22,14 @@ import shutil
import subprocess
import sys
import time
-import tomllib
+
+try:
+ import tomllib
+except ModuleNotFoundError:
+ try:
+ import tomli as tomllib
+ except ModuleNotFoundError:
+ tomllib = None
from pathlib import Path
try:
@@ -99,13 +106,15 @@ DEFAULT_CONFIG = {
def load_config() -> dict:
"""Load config from ~/.spp.toml, returning defaults if not found."""
config = DEFAULT_CONFIG.copy()
- if CONFIG_PATH.exists():
+ if CONFIG_PATH.exists() and tomllib is not None:
try:
with open(CONFIG_PATH, "rb") as f:
user_config = tomllib.load(f)
config.update(user_config)
except (tomllib.TOMLDecodeError, OSError) as e:
warn(f"Could not load config from {CONFIG_PATH}: {e}")
+ elif CONFIG_PATH.exists() and tomllib is None:
+ warn("tomllib not available (Python < 3.11). Install 'tomli' or upgrade Python to load ~/.spp.toml config.")
return config
@@ -131,7 +140,7 @@ DEMO_PROFILES = {
}
-def run(cmd: str | list, check: bool = True, capture: bool = False, **kwargs) -> subprocess.CompletedProcess:
+def run(cmd, check: bool = True, capture: bool = False, **kwargs) -> subprocess.CompletedProcess:
"""Run a command with nice defaults."""
if isinstance(cmd, str):
kwargs.setdefault("shell", True)
@@ -909,7 +918,7 @@ def cmd_sql(args):
run(docker_compose("exec", "db", "psql", "-U", "odoo", "-d", db_name))
-def _show_url(open_browser: bool = False) -> str | None:
+def _show_url(open_browser: bool = False):
"""Get the running Odoo server URL."""
service, profile = _find_running_odoo()
if not service:
diff --git a/spp_case_demo/README.rst b/spp_case_demo/README.rst
index dd82c37e..e121b47b 100644
--- a/spp_case_demo/README.rst
+++ b/spp_case_demo/README.rst
@@ -7,7 +7,7 @@ OpenSPP Case Management Demo Data
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:39196739031b49c0d69e329806b940da071ca49472f130cd28ffd45eda6459e7
+ !! source digest: sha256:force_regen
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
@@ -24,16 +24,20 @@ OpenSPP Case Management Demo Data
Demo data generator for Case Management system. Creates realistic cases
with intervention plans, home visits, progress notes, and service
-referrals. Includes 9 fixed demo stories for training and sales demos,
-plus configurable random case generation for volume testing.
+referrals. Includes 9 fixed demo stories plus 3 background cases for
+training and sales demos, and configurable volume case generation using
+Faker for locale-aware random data (non-deterministic — each run
+produces different results).
Key Capabilities
~~~~~~~~~~~~~~~~
- Generate 9 fixed demo stories with predictable personas and case
- progressions for consistent training scenarios
-- Create random volume cases with configurable distribution percentages
- for plans, visits, notes, and closures
+ progressions for consistent training scenarios, plus 3 background
+ cases (Fernandez Intake Pending, Johnson Assessment, Kim Case Closed)
+ for variety
+- Create random volume cases using Faker (non-seeded) with configurable
+ distribution percentages for plans, visits, notes, and closures
- Link generated cases to existing registrants or create standalone
cases
- Backdate case records and related activities to simulate realistic
diff --git a/spp_case_demo/readme/DESCRIPTION.md b/spp_case_demo/readme/DESCRIPTION.md
index 457abdb4..9199bee7 100644
--- a/spp_case_demo/readme/DESCRIPTION.md
+++ b/spp_case_demo/readme/DESCRIPTION.md
@@ -1,9 +1,9 @@
-Demo data generator for Case Management system. Creates realistic cases with intervention plans, home visits, progress notes, and service referrals. Includes 9 fixed demo stories for training and sales demos, plus configurable random case generation for volume testing.
+Demo data generator for Case Management system. Creates realistic cases with intervention plans, home visits, progress notes, and service referrals. Includes 9 fixed demo stories plus 3 background cases for training and sales demos, and configurable volume case generation using Faker for locale-aware random data (non-deterministic — each run produces different results).
### Key Capabilities
-- Generate 9 fixed demo stories with predictable personas and case progressions for consistent training scenarios
-- Create random volume cases with configurable distribution percentages for plans, visits, notes, and closures
+- Generate 9 fixed demo stories with predictable personas and case progressions for consistent training scenarios, plus 3 background cases (Fernandez Intake Pending, Johnson Assessment, Kim Case Closed) for variety
+- Create random volume cases using Faker (non-seeded) with configurable distribution percentages for plans, visits, notes, and closures
- Link generated cases to existing registrants or create standalone cases
- Backdate case records and related activities to simulate realistic timelines over configurable day ranges
- Create intervention plans with multiple interventions across case lifecycle stages
diff --git a/spp_case_demo/static/description/index.html b/spp_case_demo/static/description/index.html
index b8f84c13..343bfc4c 100644
--- a/spp_case_demo/static/description/index.html
+++ b/spp_case_demo/static/description/index.html
@@ -367,20 +367,24 @@
OpenSPP Case Management Demo Data
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:39196739031b49c0d69e329806b940da071ca49472f130cd28ffd45eda6459e7
+!! source digest: sha256:force_regen
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Demo data generator for Case Management system. Creates realistic cases
with intervention plans, home visits, progress notes, and service
-referrals. Includes 9 fixed demo stories for training and sales demos,
-plus configurable random case generation for volume testing.
+referrals. Includes 9 fixed demo stories plus 3 background cases for
+training and sales demos, and configurable volume case generation using
+Faker for locale-aware random data (non-deterministic — each run
+produces different results).
Key Capabilities
- Generate 9 fixed demo stories with predictable personas and case
-progressions for consistent training scenarios
-- Create random volume cases with configurable distribution percentages
-for plans, visits, notes, and closures
+progressions for consistent training scenarios, plus 3 background
+cases (Fernandez Intake Pending, Johnson Assessment, Kim Case Closed)
+for variety
+- Create random volume cases using Faker (non-seeded) with configurable
+distribution percentages for plans, visits, notes, and closures
- Link generated cases to existing registrants or create standalone
cases
- Backdate case records and related activities to simulate realistic
diff --git a/spp_demo/locale_providers/fil_PH/__init__.py b/spp_demo/locale_providers/fil_PH/__init__.py
index bba4e471..bdaa2725 100644
--- a/spp_demo/locale_providers/fil_PH/__init__.py
+++ b/spp_demo/locale_providers/fil_PH/__init__.py
@@ -4,6 +4,92 @@
class Provider(PersonProvider):
formats = ["{{first_name}} {{last_name}}"]
+ # ISO country code for res.country lookup
+ country_code = "PH"
+
+ # Address data: (city, province/region, zip_code)
+ cities = [
+ ("Quezon City", "Metro Manila", "1100"),
+ ("Manila", "Metro Manila", "1000"),
+ ("Makati", "Metro Manila", "1200"),
+ ("Taguig", "Metro Manila", "1630"),
+ ("Pasig", "Metro Manila", "1600"),
+ ("Caloocan", "Metro Manila", "1400"),
+ ("Cebu City", "Cebu", "6000"),
+ ("Davao City", "Davao del Sur", "8000"),
+ ("Iloilo City", "Iloilo", "5000"),
+ ("Zamboanga City", "Zamboanga del Sur", "7000"),
+ ("Cagayan de Oro", "Misamis Oriental", "9000"),
+ ("Bacolod", "Negros Occidental", "6100"),
+ ("General Santos", "South Cotabato", "9500"),
+ ("Batangas City", "Batangas", "4200"),
+ ("Cabanatuan", "Nueva Ecija", "3100"),
+ ("San Fernando", "Pampanga", "2000"),
+ ("Lipa", "Batangas", "4217"),
+ ("Lucena", "Quezon", "4301"),
+ ("Dagupan", "Pangasinan", "2400"),
+ ("Baguio", "Benguet", "2600"),
+ ]
+
+ street_names = [
+ "Rizal Street",
+ "Mabini Avenue",
+ "Bonifacio Drive",
+ "Quezon Boulevard",
+ "Aguinaldo Highway",
+ "Roxas Boulevard",
+ "Osmeña Street",
+ "Magsaysay Avenue",
+ "Laurel Street",
+ "Del Pilar Street",
+ "Luna Street",
+ "Jacinto Street",
+ "Silang Drive",
+ "Katipunan Avenue",
+ "Sampaguita Street",
+ "Narra Street",
+ "Acacia Lane",
+ "Mahogany Drive",
+ "Camia Street",
+ "Ilang-Ilang Street",
+ ]
+
+ # Phone format: {dN} means N random digits
+ mobile_format = "+639{d2}{d3}{d4}"
+ email_domains = ["gmail.com", "yahoo.com.ph", "outlook.com"]
+
+ birth_places = [
+ "Quezon City",
+ "Manila",
+ "Cebu City",
+ "Davao City",
+ "Makati",
+ "Iloilo City",
+ "Bacolod",
+ "Zamboanga City",
+ "Cagayan de Oro",
+ "General Santos",
+ "Batangas City",
+ "Cabanatuan",
+ "San Fernando",
+ "Baguio",
+ "Dagupan",
+ ]
+
+ # Banks: (full_name, bic_code)
+ banks = [
+ ("BDO Unibank", "BNORPHMM"),
+ ("Bank of the Philippine Islands", "BOPIPHMM"),
+ ("Metropolitan Bank and Trust", "MABORSMX"),
+ ("Land Bank of the Philippines", "TLBPPHMM"),
+ ("Philippine National Bank", "PNBMPHMM"),
+ ]
+
+ # National ID format template
+ national_id_format = "PSN-{d4}-{d4}-{d4}"
+ # Household registration format
+ household_id_format = "HH-{d4}-{d6}"
+
first_names_male = [
"Juan",
"Jose",
@@ -313,4 +399,623 @@ class Provider(PersonProvider):
"Laurel",
"Manalang",
"Natividad",
+ # Additional surnames for unique seeded group generation
+ "Abadilla",
+ "Abaya",
+ "Abeleda",
+ "Abella",
+ "Abenoja",
+ "Ablaza",
+ "Acosta",
+ "Advincula",
+ "Agcaoili",
+ "Agoncillo",
+ "Aguilar",
+ "Agtarap",
+ "Alcantara",
+ "Alcazar",
+ "Alejandro",
+ "Almeda",
+ "Almonte",
+ "Alunan",
+ "Amador",
+ "Ambrosio",
+ "Ancheta",
+ "Andaya",
+ "Anonuevo",
+ "Araneta",
+ "Arce",
+ "Arellano",
+ "Arguelles",
+ "Arias",
+ "Arroyo",
+ "Asuncion",
+ "Atienza",
+ "Avelino",
+ "Avendano",
+ "Ayala",
+ "Bacani",
+ "Bagatsing",
+ "Balbuena",
+ "Baldeo",
+ "Baldomero",
+ "Baluyot",
+ "Banzon",
+ "Barretto",
+ "Bartolome",
+ "Basa",
+ "Batac",
+ "Batungbakal",
+ "Bayani",
+ "Belarmino",
+ "Belmonte",
+ "Bernabe",
+ "Bigornia",
+ "Bondoc",
+ "Borja",
+ "Buencamino",
+ "Bulaong",
+ "Cabangon",
+ "Cabrera",
+ "Caguiat",
+ "Cajayon",
+ "Calalang",
+ "Calma",
+ "Caluag",
+ "Camara",
+ "Canlas",
+ "Capistrano",
+ "Cariaga",
+ "Carreon",
+ "Casanova",
+ "Casimiro",
+ "Centeno",
+ "Cereno",
+ "Chavez",
+ "Claudio",
+ "Clavio",
+ "Coloma",
+ "Comia",
+ "Concepcion",
+ "Constantino",
+ "Cordero",
+ "Coronel",
+ "Crisostomo",
+ "Custodio",
+ "Dacumos",
+ "Dagohoy",
+ "Dalisay",
+ "Dalusung",
+ "Dasmarinas",
+ "Dayao",
+ "Decena",
+ "Dichoso",
+ "Dimaculangan",
+ "Dimaano",
+ "Dimahilig",
+ "Dizon",
+ "Dolores",
+ "Dominguez",
+ "Dungca",
+ "Duque",
+ "Echague",
+ "Elizalde",
+ "Enriquez",
+ "Erazo",
+ "Escalante",
+ "Escueta",
+ "Esguerra",
+ "Espejo",
+ "Espiritu",
+ "Esteban",
+ "Fajardo",
+ "Feliciano",
+ "Fontanilla",
+ "Formoso",
+ "Fuentebella",
+ "Galang",
+ "Gallardo",
+ "Galvez",
+ "Gamboa",
+ "Gatmaitan",
+ "Gochangco",
+ "Guevara",
+ "Guillen",
+ "Guzman",
+ "Halili",
+ "Hermoso",
+ "Hernandez",
+ "Hilario",
+ "Ibarra",
+ "Imperial",
+ "Inciong",
+ "Javier",
+ "Jimenez",
+ "Joaquin",
+ "Kalaw",
+ "Lacson",
+ "Lagman",
+ "Lanuza",
+ "Lapuz",
+ "Lazaro",
+ "Leyva",
+ "Liban",
+ "Limbaga",
+ "Linsangan",
+ "Llanes",
+ "Locsin",
+ "Lomibao",
+ "Lugtu",
+ "Macapagal",
+ "Madamba",
+ "Magbanua",
+ "Maguindanao",
+ "Magsaysay",
+ "Malabed",
+ "Malvar",
+ "Manalastas",
+ "Mangahas",
+ "Mangubat",
+ "Manotoc",
+ "Manzano",
+ "Mapua",
+ "Maranan",
+ "Marcelo",
+ "Marcos",
+ "Marquez",
+ "Mateo",
+ "Medina",
+ "Melchor",
+ "Mercado",
+ "Miranda",
+ "Mojica",
+ "Molina",
+ "Montemayor",
+ "Montes",
+ "Moreno",
+ "Mulawin",
+ "Nacario",
+ "Nacion",
+ "Nadal",
+ "Naguit",
+ "Nalus",
+ "Narvaez",
+ "Nazareno",
+ "Neri",
+ "Nicolas",
+ "Nieto",
+ "Noble",
+ "Noche",
+ "Nolasco",
+ "Nonato",
+ "Nopuente",
+ "Novales",
+ "Nueva",
+ "Obillo",
+ "Ocampo",
+ "Ochoa",
+ "Ofreneo",
+ "Olimpo",
+ "Olivar",
+ "Ongpin",
+ "Orbeta",
+ "Ordono",
+ "Oreta",
+ "Oriel",
+ "Ornedo",
+ "Ortega",
+ "Osmena",
+ "Padilla",
+ "Pagaduan",
+ "Pagtalunan",
+ "Palacio",
+ "Palma",
+ "Pamaran",
+ "Panganiban",
+ "Paras",
+ "Pardo",
+ "Pascasio",
+ "Pastor",
+ "Patino",
+ "Penaflor",
+ "Penuela",
+ "Peralta",
+ "Perez",
+ "Pimentel",
+ "Pineda",
+ "Plata",
+ "Plaza",
+ "Ponce",
+ "Prieto",
+ "Puzon",
+ "Quezon",
+ "Quijano",
+ "Quinto",
+ "Quirino",
+ "Ramirez",
+ "Ramos",
+ "Recto",
+ "Reganit",
+ "Regalado",
+ "Regidor",
+ "Remedio",
+ "Resurreccion",
+ "Revillame",
+ "Rico",
+ "Rimando",
+ "Rizal",
+ "Robles",
+ "Roces",
+ "Rodrigo",
+ "Romero",
+ "Romulo",
+ "Ronquillo",
+ "Rosario",
+ "Roxas",
+ "Rueda",
+ "Rustia",
+ "Sabio",
+ "Sacdalan",
+ "Sagun",
+ "Salas",
+ "Salcedo",
+ "Salonga",
+ "Samonte",
+ "San Agustin",
+ "San Juan",
+ "Sanchez",
+ "Sandoval",
+ "Santiago",
+ "Sarino",
+ "Sarmiento",
+ "Sarte",
+ "Sebastian",
+ "Sevilla",
+ "Sierra",
+ "Silang",
+ "Silvestre",
+ "Singson",
+ "Sison",
+ "Solano",
+ "Soliman",
+ "Soriano",
+ "Sotto",
+ "Suarez",
+ "Suerte",
+ "Sugbo",
+ "Sumilang",
+ "Tabang",
+ "Tadena",
+ "Tagorda",
+ "Talion",
+ "Tamayo",
+ "Tanada",
+ "Tandoc",
+ "Tanjuakio",
+ "Tarog",
+ "Tatlonghari",
+ "Tayag",
+ "Teopaco",
+ "Tiangco",
+ "Tiongson",
+ "Tobias",
+ "Toledo",
+ "Tolosa",
+ "Topacio",
+ "Torralba",
+ "Torres",
+ "Trenas",
+ "Trinidad",
+ "Tugade",
+ "Tulabing",
+ "Tumaliuan",
+ "Tuazon",
+ "Ubaldo",
+ "Uson",
+ "Valderama",
+ "Valdez",
+ "Valencia",
+ "Valenzuela",
+ "Valera",
+ "Vargas",
+ "Vasquez",
+ "Velasco",
+ "Velasquez",
+ "Ventura",
+ "Vera",
+ "Vergara",
+ "Versoza",
+ "Vidal",
+ "Villacorta",
+ "Villaflor",
+ "Villalobos",
+ "Villamor",
+ "Villanueva",
+ "Villar",
+ "Villarin",
+ "Villaverde",
+ "Vinluan",
+ "Virtucio",
+ "Vizconde",
+ "Yabut",
+ "Yambao",
+ "Yangco",
+ "Yapchiongco",
+ "Yatco",
+ "Ybiernas",
+ "Ylanan",
+ "Yuchengco",
+ "Yulo",
+ "Yumul",
+ "Zabala",
+ "Zalameda",
+ "Zamudio",
+ "Zapanta",
+ "Zaragoza",
+ "Zarco",
+ "Zarsuela",
+ "Zayco",
+ "Zobel",
+ "Zulueta",
+ "Zumel",
+ "Zuniga",
+ # Additional common surnames
+ "Abad",
+ "Ablang",
+ "Acda",
+ "Aglugub",
+ "Agudo",
+ "Alcalde",
+ "Aldana",
+ "Almario",
+ "Almeda",
+ "Alvarado",
+ "Alvarez",
+ "Amarillo",
+ "Amante",
+ "Amorado",
+ "Amparo",
+ "Andal",
+ "Andujar",
+ "Angeles",
+ "Apostol",
+ "Aquiatan",
+ "Arambulo",
+ "Arcega",
+ "Arenas",
+ "Arguilla",
+ "Arnaldo",
+ "Asuque",
+ "Atilano",
+ "Avila",
+ "Bacalso",
+ "Bacungan",
+ "Bagong",
+ "Balangue",
+ "Balderas",
+ "Balingit",
+ "Banaag",
+ "Bangko",
+ "Barairo",
+ "Bartolo",
+ "Basilio",
+ "Batibot",
+ "Bayogan",
+ "Bermudo",
+ "Bilog",
+ "Bituin",
+ "Bolante",
+ "Bontuyan",
+ "Braganza",
+ "Bucatcat",
+ "Bulakan",
+ "Bunye",
+ "Bustamante",
+ "Cabal",
+ "Cabangbang",
+ "Cabigon",
+ "Cahulogan",
+ "Camarena",
+ "Campomanes",
+ "Cangco",
+ "Capulong",
+ "Cardino",
+ "Carpio",
+ "Castaneda",
+ "Catacutan",
+ "Cayetano",
+ "Celeste",
+ "Cervantes",
+ "Cinco",
+ "Cipriaso",
+ "Clarete",
+ "Colmenares",
+ "Comendador",
+ "Cordova",
+ "Cortes",
+ "Crisanto",
+ "Cuaresma",
+ "Dacanay",
+ "Dagupan",
+ "Damian",
+ "Danao",
+ "Dapitan",
+ "Dayrit",
+ "Delgado",
+ "Dequito",
+ "Diamante",
+ "Dilag",
+ "Dimailig",
+ "Diolazo",
+ "Dioquino",
+ "Dolina",
+ "Donato",
+ "Duenas",
+ "Dumpit",
+ "Dumlao",
+ "Dureza",
+ "Eboña",
+ "Eguia",
+ "Eleazar",
+ "Enotera",
+ "Ermitano",
+ "Escolta",
+ "Espino",
+ "Estacio",
+ "Estrella",
+ # More common Filipino surnames
+ "Fabricante",
+ "Factoran",
+ "Faeldon",
+ "Fermin",
+ "Figueroa",
+ "Flor",
+ "Florentino",
+ "Floresca",
+ "Fuentebella",
+ "Gaerlan",
+ "Galera",
+ "Galindez",
+ "Gallego",
+ "Gandionco",
+ "Gange",
+ "Garci",
+ "Gascon",
+ "Genuino",
+ "Gloria",
+ "Gomez",
+ "Gosiaco",
+ "Gozon",
+ "Guanzon",
+ "Guerrero",
+ "Guidote",
+ "Habito",
+ "Hagedorn",
+ "Herbosa",
+ "Hilado",
+ "Honasan",
+ "Hornedo",
+ "Hontiveros",
+ "Hufano",
+ "Ignacio",
+ "Ildefonso",
+ "Inang",
+ "Isidro",
+ "Jacinto",
+ "Jaramillo",
+ "Jopson",
+ "Jose",
+ "Joson",
+ "Jumilla",
+ "Kalaw",
+ "Labog",
+ "Lacaba",
+ "Lacsamana",
+ "Lagdameo",
+ "Lagmay",
+ "Lahoz",
+ "Lambino",
+ "Landicho",
+ "Lapid",
+ "Lardizabal",
+ "Laurel",
+ "Laxamana",
+ "Laylo",
+ "Ledesma",
+ "Legarda",
+ "Lejano",
+ "Leuterio",
+ "Leynes",
+ "Licuanan",
+ "Limjuco",
+ "Litonjua",
+ "Lizada",
+ "Llagas",
+ "Llamas",
+ "Llanes",
+ "Llenado",
+ "Lobregat",
+ "Locatelli",
+ "Lomboy",
+ "Longino",
+ "Lopa",
+ "Lopez",
+ "Lorenzana",
+ "Lorenzo",
+ "Lucero",
+ "Luistro",
+ "Luna",
+ "Lustre",
+ "Macalintal",
+ "Macasaet",
+ "Maceda",
+ "Macias",
+ "Madarang",
+ "Magsino",
+ "Magtalas",
+ "Maharlika",
+ "Malabanan",
+ "Malacad",
+ "Malapitan",
+ "Malixi",
+ "Malicdem",
+ "Mallari",
+ "Mandanas",
+ "Mangrobang",
+ "Manigbas",
+ "Manlapaz",
+ "Manzanero",
+ "Marasigan",
+ "Marfil",
+ "Masangkay",
+ "Matias",
+ "Maximo",
+ "Mayuga",
+ "Medino",
+ "Meliton",
+ "Mendez",
+ "Mendiola",
+ "Mercene",
+ "Mesina",
+ "Miraflores",
+ "Mislang",
+ "Mojares",
+ "Molave",
+ "Montalban",
+ "Montenegro",
+ "Morales",
+ "Moreno",
+ "Mula",
+ "Nambatac",
+ "Nario",
+ "Nepomuceno",
+ "Nicanor",
+ "Nievera",
+ "Nograles",
+ "Nolledo",
+ "Nunez",
+ "Oca",
+ "Olaguer",
+ "Olarte",
+ "Olmedo",
+ "Ongsip",
+ "Ople",
+ "Ordonez",
+ "Orosa",
+ "Ortigas",
+ "Osias",
+ "Ozaeta",
+ "Pabalinas",
+ "Pacquiao",
+ "Paguia",
+ "Palacios",
+ "Palomo",
+ "Panday",
+ "Pangasinan",
+ "Panlilio",
+ "Panlaqui",
+ "Paranaque",
+ "Pascua",
+ "Pasion",
+ "Paterno",
]
diff --git a/spp_demo/locale_providers/fr_TG/__init__.py b/spp_demo/locale_providers/fr_TG/__init__.py
index e17b9c12..0ba3ee83 100644
--- a/spp_demo/locale_providers/fr_TG/__init__.py
+++ b/spp_demo/locale_providers/fr_TG/__init__.py
@@ -4,6 +4,73 @@
class Provider(PersonProvider):
formats = ["{{first_name}} {{last_name}}"]
+ # ISO country code
+ country_code = "TG"
+
+ # Address data: (city, region, zip_code)
+ cities = [
+ ("Lomé", "Maritime", "01 BP"),
+ ("Kara", "Kara", "04 BP"),
+ ("Sokodé", "Centrale", "03 BP"),
+ ("Atakpamé", "Plateaux", "02 BP"),
+ ("Kpalimé", "Plateaux", "02 BP"),
+ ("Dapaong", "Savanes", "05 BP"),
+ ("Tsévié", "Maritime", "01 BP"),
+ ("Aného", "Maritime", "01 BP"),
+ ("Bassar", "Kara", "04 BP"),
+ ("Notsé", "Plateaux", "02 BP"),
+ ("Tabligbo", "Maritime", "01 BP"),
+ ("Badou", "Plateaux", "02 BP"),
+ ("Vogan", "Maritime", "01 BP"),
+ ("Niamtougou", "Kara", "04 BP"),
+ ("Mango", "Savanes", "05 BP"),
+ ]
+
+ street_names = [
+ "Boulevard de la Paix",
+ "Rue du Commerce",
+ "Avenue de la Libération",
+ "Rue de la Gare",
+ "Boulevard du Mono",
+ "Avenue de la Nouvelle Marche",
+ "Rue des Nimes",
+ "Boulevard Circulaire",
+ "Avenue du 24 Janvier",
+ "Rue de l'Hôpital",
+ "Avenue de Sarakawa",
+ "Rue du Grand Marché",
+ "Boulevard de la République",
+ "Avenue de la Chance",
+ "Rue de l'Indépendance",
+ ]
+
+ mobile_format = "+228 9{d1} {d2} {d2} {d2}"
+ email_domains = ["gmail.com", "yahoo.fr", "hotmail.com"]
+
+ birth_places = [
+ "Lomé",
+ "Kara",
+ "Sokodé",
+ "Atakpamé",
+ "Kpalimé",
+ "Dapaong",
+ "Tsévié",
+ "Aného",
+ "Bassar",
+ "Notsé",
+ ]
+
+ banks = [
+ ("Ecobank Togo", "ABORSMXX"),
+ ("Union Togolaise de Banque", "UTBKTGTG"),
+ ("BTCI", "BTCITGTG"),
+ ("Banque Populaire pour l'Épargne et le Crédit", "BPECTGTG"),
+ ("Orabank Togo", "ORANTGTG"),
+ ]
+
+ national_id_format = "TG-{d4}-{d4}-{d2}"
+ household_id_format = "MEN-{d4}-{d6}"
+
first_names_male = [
"Koffi",
"Kodjo",
@@ -312,4 +379,469 @@ class Provider(PersonProvider):
"Yawovi",
"Zankli",
"Zinsou",
+ # Additional surnames for unique seeded group generation
+ "Abalo",
+ "Abitor",
+ "Ablam",
+ "Adabra",
+ "Adade",
+ "Adama",
+ "Adanou",
+ "Adjakly",
+ "Adjignon",
+ "Adodo",
+ "Afandou",
+ "Afelu",
+ "Agbaglo",
+ "Agbedanu",
+ "Agbemafle",
+ "Agbetiafa",
+ "Agbezuge",
+ "Agossa",
+ "Ahiave",
+ "Ahlin",
+ "Aholou",
+ "Ajagba",
+ "Akator",
+ "Akitani",
+ "Aklesso",
+ "Akonde",
+ "Akouete",
+ "Akpaki",
+ "Akpamou",
+ "Akpedze",
+ "Akpene",
+ "Alagbe",
+ "Aledji",
+ "Alipui",
+ "Alognon",
+ "Amaizo",
+ "Amavi",
+ "Amefia",
+ "Amekudzi",
+ "Amela",
+ "Amenyah",
+ "Ametsipe",
+ "Amoussou",
+ "Anato",
+ "Anipah",
+ "Ankou",
+ "Apedo",
+ "Apetogbo",
+ "Aquereburu",
+ "Ashiagbor",
+ "Assem",
+ "Assignon",
+ "Assogba",
+ "Atayi",
+ "Atidepe",
+ "Atignon",
+ "Atikpo",
+ "Atsou",
+ "Avevor",
+ "Avide",
+ "Avlessi",
+ "Awaga",
+ "Awudu",
+ "Ayaba",
+ "Ayassou",
+ "Aziankou",
+ "Azilan",
+ "Baglo",
+ "Bakpe",
+ "Baliki",
+ "Bamaze",
+ "Banito",
+ "Batale",
+ "Bedja",
+ "Belo",
+ "Biova",
+ "Bitho",
+ "Bodjona",
+ "Bogla",
+ "Bokovi",
+ "Bolou",
+ "Bonito",
+ "Bossou",
+ "Dadjo",
+ "Dagadu",
+ "Dake",
+ "Damtse",
+ "Dansou",
+ "Degbe",
+ "Dekpe",
+ "Denyigba",
+ "Devou",
+ "Djagba",
+ "Djato",
+ "Djossou",
+ "Dotse",
+ "Duho",
+ "Dzahini",
+ "Dzamesi",
+ "Dzidula",
+ "Edoh",
+ "Efio",
+ "Egblewogbe",
+ "Eklu",
+ "Ekpe",
+ "Elegbe",
+ "Etou",
+ "Ewovor",
+ "Fianyo",
+ "Fiawoo",
+ "Foli",
+ "Folly",
+ "Gadegbeku",
+ "Gake",
+ "Galo",
+ "Ganyo",
+ "Gbadago",
+ "Gbeasor",
+ "Gbeku",
+ "Gbikpi",
+ "Gogovi",
+ "Goka",
+ "Gouda",
+ "Hessou",
+ "Hodabalo",
+ "Hounnou",
+ "Idohou",
+ "Issifou",
+ "Kadanga",
+ "Kagbara",
+ "Kakpo",
+ "Kalipe",
+ "Kampo",
+ "Kanfitine",
+ "Katakiti",
+ "Kavege",
+ "Kekeli",
+ "Keteku",
+ "Klutse",
+ "Kodjo",
+ "Koffi",
+ "Komlagan",
+ "Kondo",
+ "Kossi",
+ "Kpade",
+ "Kpegba",
+ "Kudjoe",
+ # More Togolese surnames for unique seeded group generation
+ "Kuevi",
+ "Kumako",
+ "Kutukpa",
+ "Kwadzo",
+ "Kwami",
+ "Kwamiga",
+ "Kwasi",
+ "Kweku",
+ "Labite",
+ "Lagnika",
+ "Lakoussan",
+ "Lamboni",
+ "Lamoubou",
+ "Latevi",
+ "Lawani",
+ "Ledo",
+ "Legba",
+ "Limam",
+ "Litcho",
+ "Lodovie",
+ "Logossou",
+ "Lokossou",
+ "Loma",
+ "Lomayi",
+ "Loncke",
+ "Loumon",
+ "Maba",
+ "Mabasso",
+ "Madja",
+ "Mafon",
+ "Maglo",
+ "Mahama",
+ "Maizi",
+ "Mako",
+ "Malou",
+ "Mama",
+ "Maman",
+ "Mamba",
+ "Mana",
+ "Mandjam",
+ "Mangue",
+ "Manka",
+ "Mapa",
+ "Masika",
+ "Matou",
+ "Mawu",
+ "Mazou",
+ "Meba",
+ "Mede",
+ "Medji",
+ "Mefou",
+ "Mehou",
+ "Mekpo",
+ "Mensavi",
+ "Metowogo",
+ "Mignonsin",
+ "Minoungou",
+ "Modjom",
+ "Modzie",
+ "Moglo",
+ "Moisan",
+ "Mokpe",
+ "Monku",
+ "Mossi",
+ "Moumouni",
+ "Moussa",
+ "Mouzou",
+ "Nadjombe",
+ "Nagou",
+ "Nakou",
+ "Nalohou",
+ "Namaro",
+ "Nambou",
+ "Namoro",
+ "Nana",
+ "Napo",
+ "Nassar",
+ "Natre",
+ "Nawiye",
+ "Nayo",
+ "Ndao",
+ "Nebou",
+ "Nemi",
+ "Nenonene",
+ "Niamke",
+ "Nika",
+ "Nikabou",
+ "Nima",
+ "Ninvie",
+ "Nkegbe",
+ "Nkouaya",
+ "Noameshie",
+ "Nokou",
+ "Notor",
+ "Notsron",
+ "Nowou",
+ "Nubukpo",
+ "Nudro",
+ "Nyaku",
+ "Nyametso",
+ "Nyuiadzi",
+ "Obodai",
+ "Obuobi",
+ "Odame",
+ "Odjo",
+ "Odou",
+ "Ofori",
+ "Ogou",
+ "Ohene",
+ "Ohin",
+ "Oklu",
+ "Okpoli",
+ "Olou",
+ "Olympio",
+ "Opoku",
+ "Osseni",
+ "Otsou",
+ "Ouadja",
+ "Ouandjame",
+ "Ouanilo",
+ "Ouro",
+ "Owusu",
+ "Pabi",
+ "Padaro",
+ "Pagbe",
+ "Paja",
+ "Pakou",
+ "Palanga",
+ "Palenfo",
+ "Pali",
+ "Panassa",
+ "Pane",
+ "Panfilo",
+ "Papa",
+ "Passake",
+ "Passere",
+ "Patchale",
+ "Patousse",
+ "Pedjo",
+ "Pekpe",
+ "Pere",
+ "Petchezi",
+ "Pete",
+ "Piabou",
+ "Piase",
+ "Pickou",
+ "Pilipe",
+ "Piou",
+ "Pitang",
+ "Plange",
+ "Podji",
+ "Pomi",
+ "Pouya",
+ "Poyodi",
+ "Pray",
+ "Pugbe",
+ "Quaye",
+ "Sakari",
+ "Sakiti",
+ "Salami",
+ "Samari",
+ "Sangnidji",
+ "Sanon",
+ "Sare",
+ "Sarigbe",
+ "Sasraku",
+ "Savi",
+ "Segbeaya",
+ "Segli",
+ "Segniagbeto",
+ "Seke",
+ "Selasie",
+ "Semedo",
+ "Senu",
+ "Sessou",
+ "Setor",
+ "Sewu",
+ "Simtaro",
+ "Sinko",
+ "Sodzi",
+ "Soedji",
+ "Soglo",
+ "Sokpe",
+ "Somabe",
+ "Sonam",
+ "Sonhaye",
+ "Sossah",
+ "Sossavi",
+ "Soude",
+ "Souleymane",
+ "Sounon",
+ "Suanu",
+ "Tagba",
+ "Tagoada",
+ "Taikora",
+ "Takassi",
+ "Taki",
+ "Tala",
+ "Talba",
+ "Tamaklo",
+ "Tambera",
+ "Tame",
+ "Tandoh",
+ "Tangara",
+ "Tanko",
+ "Tano",
+ "Taoki",
+ "Tapoba",
+ "Tara",
+ "Tatao",
+ "Tavi",
+ "Tawema",
+ "Tayo",
+ "Tchable",
+ "Tchagba",
+ "Tchala",
+ "Tcham",
+ "Tchankoni",
+ "Tchao",
+ "Tchapo",
+ "Tchassim",
+ "Tchegnon",
+ "Tchekpi",
+ "Tchindo",
+ "Tchoukou",
+ "Tebele",
+ "Tegbe",
+ "Tekou",
+ "Telle",
+ "Tepe",
+ "Tete",
+ "Teteh",
+ "Tevi",
+ "Teyou",
+ "Tiburce",
+ "Tikpi",
+ "Timite",
+ "Tindano",
+ "Titikpina",
+ "Togbe",
+ "Togbui",
+ "Toklo",
+ "Tombiti",
+ "Tonou",
+ "Tossou",
+ "Toure",
+ "Toussa",
+ "Traore",
+ "Tsatsu",
+ "Tsekpo",
+ "Tseve",
+ "Tsigbe",
+ "Tsikudo",
+ "Tsikata",
+ "Tutu",
+ "Udzi",
+ "Vinyo",
+ "Vodouhe",
+ "Vonor",
+ "Waibi",
+ "Walana",
+ "Wavi",
+ "Wemegah",
+ "Woanyah",
+ "Woedzo",
+ "Woenya",
+ "Wogbe",
+ "Woledzi",
+ "Womegah",
+ "Wona",
+ "Wonyra",
+ "Worlanyo",
+ "Wutoh",
+ "Yabre",
+ "Yakpo",
+ "Yamadjako",
+ "Yamega",
+ "Yangni",
+ "Yao",
+ "Yaotse",
+ "Yawotse",
+ "Yebuah",
+ "Yedji",
+ "Yegbe",
+ "Yentema",
+ "Yiga",
+ "Yohou",
+ "Yondo",
+ "Yotchou",
+ "Youmbi",
+ "Zaaki",
+ "Zabre",
+ "Zagou",
+ "Zakari",
+ "Zaka",
+ "Zali",
+ "Zamba",
+ "Zanvo",
+ "Zanu",
+ "Zaou",
+ "Zemba",
+ "Zevi",
+ "Ziame",
+ "Ziganou",
+ "Zikpi",
+ "Zime",
+ "Zingan",
+ "Zinzou",
+ "Zogbedor",
+ "Zolia",
+ "Zonvide",
+ "Zorome",
+ "Zossou",
+ "Zoungrana",
+ "Zounon",
+ "Zouzoua",
]
diff --git a/spp_demo/locale_providers/si_LK/__init__.py b/spp_demo/locale_providers/si_LK/__init__.py
index 5a21b851..75c2541a 100644
--- a/spp_demo/locale_providers/si_LK/__init__.py
+++ b/spp_demo/locale_providers/si_LK/__init__.py
@@ -4,6 +4,73 @@
class Provider(PersonProvider):
formats = ["{{first_name}} {{last_name}}"]
+ # ISO country code
+ country_code = "LK"
+
+ # Address data: (city, province, zip_code)
+ cities = [
+ ("Colombo", "Western Province", "00100"),
+ ("Kandy", "Central Province", "20000"),
+ ("Galle", "Southern Province", "80000"),
+ ("Jaffna", "Northern Province", "40000"),
+ ("Negombo", "Western Province", "11500"),
+ ("Batticaloa", "Eastern Province", "30000"),
+ ("Trincomalee", "Eastern Province", "31000"),
+ ("Matara", "Southern Province", "81000"),
+ ("Anuradhapura", "North Central Province", "50000"),
+ ("Kurunegala", "North Western Province", "60000"),
+ ("Ratnapura", "Sabaragamuwa Province", "70000"),
+ ("Badulla", "Uva Province", "90000"),
+ ("Gampaha", "Western Province", "11000"),
+ ("Kalutara", "Western Province", "12000"),
+ ("Nuwara Eliya", "Central Province", "22200"),
+ ]
+
+ street_names = [
+ "Galle Road",
+ "Kandy Road",
+ "Duplication Road",
+ "Havelock Road",
+ "Baseline Road",
+ "High Level Road",
+ "Bauddhaloka Mawatha",
+ "Wijerama Mawatha",
+ "Dharmapala Mawatha",
+ "Peradeniya Road",
+ "Temple Street",
+ "Lotus Road",
+ "Chatham Street",
+ "York Street",
+ "Dam Street",
+ ]
+
+ mobile_format = "+947{d1}{d3}{d4}"
+ email_domains = ["gmail.com", "yahoo.com", "outlook.com"]
+
+ birth_places = [
+ "Colombo",
+ "Kandy",
+ "Galle",
+ "Jaffna",
+ "Negombo",
+ "Matara",
+ "Kurunegala",
+ "Batticaloa",
+ "Anuradhapura",
+ "Ratnapura",
+ ]
+
+ banks = [
+ ("Bank of Ceylon", "BABORLKLXXX"),
+ ("People's Bank", "PABORLKLXXX"),
+ ("Commercial Bank of Ceylon", "CABORLKLXXX"),
+ ("Hatton National Bank", "HABORLKLXXX"),
+ ("Sampath Bank", "SABORLKLXXX"),
+ ]
+
+ national_id_format = "{d9}V"
+ household_id_format = "HH-{d4}-{d6}"
+
first_names_male = [
"Aruna",
"Chamara",
@@ -350,4 +417,435 @@ class Provider(PersonProvider):
"Wijemanne",
"Ratwatte",
"Jayaweera",
+ # Additional surnames for unique seeded group generation
+ "Abeysekara",
+ "Abeysiriwardena",
+ "Abeywardena",
+ "Amarakoon",
+ "Amarasena",
+ "Amerasinghe",
+ "Ananda",
+ "Ariyapala",
+ "Ariyaratne",
+ "Athukorala",
+ "Balasooriya",
+ "Balasuriya",
+ "Bambaradeniya",
+ "Basnayake",
+ "Bogahawatte",
+ "Bogoda",
+ "Chandrasekara",
+ "Chandrasena",
+ "Dassanayake",
+ "Dayaratne",
+ "Deraniyagala",
+ "Devapura",
+ "Dharmaratne",
+ "Dharmasena",
+ "Dharmawardena",
+ "Dharmadasa",
+ "Dissanayaka",
+ "Ekanayake",
+ "Ellepola",
+ "Fonseka",
+ "Gajadeera",
+ "Gamachchi",
+ "Gamalath",
+ "Ganegoda",
+ "Goonaratne",
+ "Goonesekera",
+ "Goonetilleke",
+ "Gunapala",
+ "Gunatilaka",
+ "Gunathilake",
+ "Harasgama",
+ "Heiyanthuduwa",
+ "Herath",
+ "Hulangamuwa",
+ "Illangaratne",
+ "Imbuldeniya",
+ "Indrajith",
+ "Jayakody",
+ "Jayamaha",
+ "Jayamanne",
+ "Jayarathne",
+ "Jayasena",
+ "Jayatilaka",
+ "Jayawardana",
+ "Kadawathage",
+ "Kadurugamuwa",
+ "Kahatapitiya",
+ "Kalubowila",
+ "Kamburugamuwa",
+ "Kanangara",
+ "Kapurubandara",
+ "Karunasena",
+ "Katugampola",
+ "Keragala",
+ "Kiridena",
+ "Kobbekaduwa",
+ "Kodikara",
+ "Kotalawala",
+ "Kudaliyanage",
+ "Kulasinghe",
+ "Kulathunga",
+ "Kumarasiri",
+ "Lakshman",
+ "Liyanaarachchi",
+ "Liyanage",
+ "Lokuge",
+ "Madanayake",
+ "Madawala",
+ "Madurapperuma",
+ "Mahanama",
+ "Mahasena",
+ "Mahawatta",
+ "Malalagama",
+ "Mallikarachchi",
+ "Manchanayake",
+ "Medagama",
+ "Meegahawatte",
+ "Mohottige",
+ "Muthukuda",
+ "Narangoda",
+ "Nawarathne",
+ "Nayanapriya",
+ "Nishantha",
+ "Obeyesekere",
+ "Obeysekara",
+ "Opatha",
+ "Padmasiri",
+ "Palihawadana",
+ "Palliyaguru",
+ "Paranagama",
+ "Pathmasiri",
+ "Payagala",
+ "Peiris",
+ "Pethiyagoda",
+ "Piyasena",
+ "Piyatilake",
+ "Polgampola",
+ "Polonnaruwa",
+ "Ponnamperuma",
+ "Premachandra",
+ "Premaratne",
+ "Priyankara",
+ "Rajakaruna",
+ "Rajapakse",
+ "Rambukwella",
+ "Ranasinghe",
+ "Ranatunga",
+ "Ranawaka",
+ "Rathnasiri",
+ "Ratnayaka",
+ "Rupasinghe",
+ "Samaranayake",
+ "Samarathunga",
+ "Samaraweera",
+ "Sandaruwan",
+ "Saputhanthri",
+ "Senanayaka",
+ "Senerath",
+ "Seneviratna",
+ "Siriwardana",
+ "Subasinghe",
+ "Sugathadasa",
+ "Sumathipala",
+ "Tennakoon",
+ "Thenuwara",
+ "Thilakaratne",
+ "Thilakarathna",
+ "Tillekeratne",
+ "Udugama",
+ "Unamboowe",
+ "Waidyasekara",
+ "Wanigasekera",
+ "Warakagoda",
+ "Warnakulasuriya",
+ "Weeraratne",
+ "Weerasooriya",
+ "Weerathunga",
+ "Welgama",
+ "Wickremasinghe",
+ "Wijegoonewardena",
+ "Wijeratne",
+ "Wijesuriya",
+ "Withanage",
+ # More Sinhalese surnames for unique seeded group generation
+ "Abeykoon",
+ "Abeynayake",
+ "Abeyrathna",
+ "Abeythilake",
+ "Adikaram",
+ "Alahakoon",
+ "Alawathuwala",
+ "Aluthgama",
+ "Aluvihare",
+ "Ambagahawita",
+ "Ambepitiya",
+ "Ameratunga",
+ "Ampitiya",
+ "Anandagoda",
+ "Anurudhdha",
+ "Ariyadasa",
+ "Arsakulasuriya",
+ "Athauda",
+ "Athulathmudali",
+ "Attygalle",
+ "Balachandra",
+ "Bandaranayake",
+ "Batagoda",
+ "Bogahapitiya",
+ "Bulathsinhala",
+ "Chandrakumara",
+ "Chandrapala",
+ "Coomaraswamy",
+ "Dahanayake",
+ "Daluwatte",
+ "Dambagolla",
+ "Damunupola",
+ "Delgoda",
+ "Denipitiya",
+ "Devapriya",
+ "Devendra",
+ "Dhammika",
+ "Dhanushka",
+ "Dissanaike",
+ "Dunuwila",
+ "Edirisinghe",
+ "Ediriweera",
+ "Elawala",
+ "Ellepola",
+ "Epa",
+ "Eriyagama",
+ "Ethugala",
+ "Gajaman",
+ "Galagama",
+ "Galapatti",
+ "Gallage",
+ "Gamlath",
+ "Gammanpila",
+ "Gangabadage",
+ "Gannoruwa",
+ "Godahewa",
+ "Godakumbura",
+ "Gonagala",
+ "Gonapinuwala",
+ "Goonawardana",
+ "Gunasinghe",
+ "Gunathilaka",
+ "Gunawansa",
+ "Gunaweera",
+ "Hapugoda",
+ "Hapukotuwa",
+ "Haturusinghe",
+ "Heendeniya",
+ "Hettiarachchi",
+ "Hettihewa",
+ "Hewage",
+ "Hewavitharana",
+ "Hulangamuwa",
+ "Hunukumbura",
+ "Iddamalgoda",
+ "Imbulgoda",
+ "Imiyage",
+ "Induruwa",
+ "Irugalbandara",
+ "Isurukumara",
+ "Jagoda",
+ "Janapriya",
+ "Jayalath",
+ "Jayampathi",
+ "Jayantha",
+ "Jayasingha",
+ "Jayathissa",
+ "Jayawardene",
+ "Kahandagama",
+ "Kahawandala",
+ "Kailasapathy",
+ "Kalansuriya",
+ "Kaliyadasa",
+ "Kaluarage",
+ "Kaluthanthri",
+ "Kandegedara",
+ "Kandepola",
+ "Kapilananda",
+ "Kariyawasam",
+ "Karuhapperuma",
+ "Kasthuriarachchi",
+ "Katuwana",
+ "Kekulawala",
+ "Keppitiyagama",
+ "Keragodage",
+ "Keraminiyage",
+ "Kithsiri",
+ "Kolongahapitiya",
+ "Komasaru",
+ "Konara",
+ "Kottachchi",
+ "Kotelawala",
+ "Kudabanda",
+ "Kudahettige",
+ "Kulasena",
+ "Kulathunge",
+ "Kumaragamage",
+ "Kumarihamy",
+ "Kurukulasuriya",
+ "Kuruppu",
+ "Lanarolle",
+ "Lekamge",
+ "Lekamwasam",
+ "Lihiniyage",
+ "Liyana",
+ "Liyanarachchi",
+ "Lokubandara",
+ "Lokugama",
+ "Lokuge",
+ "Madawela",
+ "Madugalle",
+ "Madumadawa",
+ "Madurasinghe",
+ "Magalkumbura",
+ "Mahagedara",
+ "Mahaguruge",
+ "Mahanama",
+ "Mahatantila",
+ "Mahawela",
+ "Mahipala",
+ "Mahithuduwa",
+ "Maitipe",
+ "Mallawaarachchi",
+ "Manamperi",
+ "Manathunga",
+ "Mangalika",
+ "Matale",
+ "Medawatta",
+ "Meegahage",
+ "Meepe",
+ "Mendis",
+ "Menikdiwela",
+ "Mirihana",
+ "Molagoda",
+ "Molligoda",
+ "Mudannayake",
+ "Mudalige",
+ "Muhandiram",
+ "Munaweera",
+ "Muthuthantri",
+ "Nagahawatte",
+ "Naotunna",
+ "Nawagamuwa",
+ "Nawinna",
+ "Neelagama",
+ "Nilame",
+ "Nissanka",
+ "Niyangoda",
+ "Nugapitiya",
+ "Nugegoda",
+ "Obinapitiya",
+ "Olaganwatte",
+ "Olcott",
+ "Ovitigama",
+ "Padukka",
+ "Pallawela",
+ "Pallegama",
+ "Palliyaguruge",
+ "Panabokke",
+ "Panagoda",
+ "Panapitiya",
+ "Panditha",
+ "Pannala",
+ "Pannilage",
+ "Paranavithana",
+ "Pathiraja",
+ "Pathirana",
+ "Paththini",
+ "Payagalage",
+ "Peiris",
+ "Perera",
+ "Pethiyagoda",
+ "Pinidiya",
+ "Piyaratne",
+ "Piyasena",
+ "Piyatissa",
+ "Poddiwela",
+ "Polwatte",
+ "Porutota",
+ "Premadasa",
+ "Premaratna",
+ "Priyantha",
+ "Pugoda",
+ "Punchi",
+ "Pushpakumara",
+ "Rajakarige",
+ "Rajapaksha",
+ "Ramanayake",
+ "Rambanda",
+ "Ranaweera",
+ "Rathnapala",
+ "Ratnasiri",
+ "Rohitha",
+ "Rupasingha",
+ "Sahabandu",
+ "Samarakkody",
+ "Samarapala",
+ "Samarasekera",
+ "Samarasiri",
+ "Samarawickrama",
+ "Sandakelum",
+ "Sandaruwan",
+ "Sangakkara",
+ "Sapumohotti",
+ "Saranankara",
+ "Senaratne",
+ "Senevirathne",
+ "Sirimanna",
+ "Siriwardhana",
+ "Sugathapala",
+ "Sumanasekera",
+ "Sumanawathie",
+ "Sundarapperuma",
+ "Suriyaarachchi",
+ "Talawila",
+ "Talpawila",
+ "Tennekoon",
+ "Thenabadu",
+ "Thilakarathne",
+ "Thotawatte",
+ "Tillakawardane",
+ "Tudawe",
+ "Udawatte",
+ "Udugampola",
+ "Udupotha",
+ "Ukwatte",
+ "Ulpathage",
+ "Undugoda",
+ "Unuwaturage",
+ "Upasena",
+ "Vaidyalingam",
+ "Vidanalage",
+ "Vitharana",
+ "Waduge",
+ "Wagaarachchi",
+ "Walawwage",
+ "Wanasundara",
+ "Wanigatunge",
+ "Wanniarachchi",
+ "Warnasooriya",
+ "Wattage",
+ "Weerasingha",
+ "Welagedara",
+ "Welipitiya",
+ "Wettasinghe",
+ "Wickramasuriya",
+ "Wijegunasekera",
+ "Wijekulasuriya",
+ "Wijesingha",
+ "Wijethilake",
+ "Wijethunga",
+ "Wimaladharma",
+ "Wimalasena",
+ "Wiratunga",
+ "Yalawaththa",
+ "Yapabandara",
]
diff --git a/spp_demo/models/demo_data_generator.py b/spp_demo/models/demo_data_generator.py
index 1aff4218..42ac17a4 100644
--- a/spp_demo/models/demo_data_generator.py
+++ b/spp_demo/models/demo_data_generator.py
@@ -1468,7 +1468,7 @@ def _create_household_story(self, story):
# Build group values
vals = {
"demo_data_group_generator_id": self.id,
- "name": head_info.get("name", story["name"]),
+ "name": story["name"],
"is_registrant": True,
"is_group": True,
"registration_date": registration_date,
diff --git a/spp_demo/models/demo_stories.py b/spp_demo/models/demo_stories.py
index fc16124e..b1fd3eb2 100644
--- a/spp_demo/models/demo_stories.py
+++ b/spp_demo/models/demo_stories.py
@@ -15,6 +15,19 @@
# Reserved names that should not be used for random volume generation
RESERVED_NAMES = [
+ # Household group names (family name only)
+ "Santos",
+ "Dela Cruz",
+ "Morales",
+ "Aquino",
+ "Reyes",
+ "Bautista",
+ "Pangilinan",
+ "Navarro",
+ "Gutierrez",
+ "Martinez",
+ "Castillo",
+ # Individual story names
"Maria Santos",
"Juan Dela Cruz",
"Rosa Garcia",
@@ -22,22 +35,23 @@
"Ana Mendoza",
"Carlos Morales",
"Elena Morales",
- "Ibrahim Hassan",
- "Fatima Al-Rahman",
+ "Ramon Gutierrez",
+ "Teresa Villanueva",
"Luis Fernandez",
- "Mary Johnson",
- "Ahmed Said",
- "Grace Okonkwo",
- "David Kim",
+ "Lorna Pascual",
+ "Roberto Castillo",
+ "Maricel Ramos",
+ "Eduardo Tan",
# Additional household members
"Marco Morales",
"Sofia Morales",
"Luis Morales",
- # New household stories
- "Amina Osman",
- "Yusuf Osman",
- "Layla Osman",
- "Hassan Osman",
+ # Aquino household (amina_osman_household)
+ "Rosario Aquino",
+ "Daniel Aquino",
+ "Angela Aquino",
+ "Rafael Aquino",
+ # Reyes multigenerational household
"Jose Reyes Sr",
"Carmen Reyes",
"Miguel Reyes",
@@ -46,19 +60,19 @@
"Lucia Reyes",
"Antonio Reyes",
"Isabella Reyes",
- "Chen Wei",
- "Chen Mei",
- "Chen Ling",
- "Chen Jun",
- "Chen Xiao",
- "Chen Yan",
- "Chen Bo",
- "Manuel Santos",
- "Gloria Santos",
- "James Nguyen",
- "Linda Nguyen",
- "Michael Nguyen",
- "Sarah Nguyen",
+ "Eduardo Bautista",
+ "Carmen Bautista",
+ "Patricia Bautista",
+ "Fernando Bautista",
+ "Lucia Bautista",
+ "Rosalie Bautista",
+ "Antonio Bautista",
+ "Manuel Pangilinan",
+ "Gloria Pangilinan",
+ "Ricardo Navarro",
+ "Lourdes Navarro",
+ "Eduardo Navarro",
+ "Cristina Navarro",
# Story 9 - Martinez household
"David Martinez",
"Sofia Martinez",
@@ -68,19 +82,19 @@
"Roberto Garcia",
"Maria Garcia",
"Carlos Garcia",
- "Santos Family",
- "Jose Santos",
- "Ana Santos",
- "Mia Santos",
- "Cruz Family",
- "Pedro Cruz",
- "Teresa Cruz",
- "Juan Cruz",
- "Maria Cruz",
- "Reyes Family",
- "Ramon Reyes",
- "Elena Reyes",
- "Lucia Reyes",
+ "Tolentino Family",
+ "Jose Tolentino",
+ "Ana Tolentino",
+ "Mia Tolentino",
+ "Salazar Family",
+ "Pedro Salazar",
+ "Teresa Salazar",
+ "Juan Salazar",
+ "Maria Salazar",
+ "Mercado Family",
+ "Ramon Mercado",
+ "Elena Mercado",
+ "Lucia Mercado",
"Ramos Family",
"Antonio Ramos",
"Rosa Ramos",
@@ -91,40 +105,42 @@
DEMO_STORIES = [
{
"id": "maria_santos",
- "name": "Maria Santos",
- "type": "farmer",
+ "name": "Santos",
+ "type": "household",
"story_title": "The Success Story",
"story_description": "Happy path from registration to graduation",
"profile": {
- "gender": "female",
- "age": 42,
- "education": "primary",
- "farm_size": 2.5,
- "farm_size_hectares": 2.5, # CEL: Input Subsidy eligibility
- "farm_type": "crop",
- "main_crop": "rice",
+ "head": {"name": "Maria Santos", "gender": "female", "age": 42},
+ "spouse": {"name": "Ricardo Santos", "gender": "male", "age": 44},
+ "children": [
+ {"name": "Sofia Santos", "gender": "female", "age": 14},
+ {"name": "Miguel Santos", "gender": "male", "age": 10},
+ ],
+ "adults": [
+ {"name": "Lola Santos", "gender": "female", "age": 68, "relation": "parent"},
+ ],
"district": "Northern District",
"marital_status": "married",
"household_size": 5,
},
"journey": [
- {"action": "register", "days_back": 180},
- {"action": "add_farm_details", "days_back": 175},
+ {"action": "register_household", "days_back": 180},
+ {"action": "add_household_members", "days_back": 175},
{
"action": "verify_eligibility",
- "program": "Input Subsidy Program",
- "cel_check": "farm_size",
+ "program": "Cash Transfer Program",
+ "cel_check": "income",
"days_back": 152,
},
- {"action": "enroll_program", "program": "Input Subsidy Program", "days_back": 150},
+ {"action": "enroll_program", "program": "Cash Transfer Program", "days_back": 150},
{"action": "create_event", "event_type": "training", "days_back": 145},
- {"action": "create_payment", "amount": 200, "status": "paid", "days_back": 120},
- {"action": "create_payment", "amount": 200, "status": "paid", "days_back": 90},
- {"action": "create_payment", "amount": 200, "status": "paid", "days_back": 60},
+ {"action": "create_payment", "amount": 150, "status": "paid", "days_back": 120},
+ {"action": "create_payment", "amount": 150, "status": "paid", "days_back": 90},
+ {"action": "create_payment", "amount": 150, "status": "paid", "days_back": 60},
{"action": "graduate_program", "days_back": 30},
],
"demo_points": [
- "Complete farmer profile with all details filled",
+ "Complete household profile with all members",
"Program enrollment with full cycle",
"Payment history showing successful disbursements",
"Graduation status",
@@ -132,23 +148,23 @@
},
{
"id": "juan_dela_cruz",
- "name": "Juan Dela Cruz",
- "type": "farmer",
+ "name": "Dela Cruz",
+ "type": "household",
"story_title": "GRM Resolution",
"story_description": "Demonstrate grievance handling workflow",
"profile": {
- "gender": "male",
- "age": 38,
- "education": "secondary",
- "farm_size": 1.0,
- "farm_size_hectares": 1.0, # CEL: Farm size for eligibility
- "farm_type": "mixed",
- "main_crop": "vegetables",
+ "head": {"name": "Juan Dela Cruz", "gender": "male", "age": 38},
+ "spouse": {"name": "Ana Dela Cruz", "gender": "female", "age": 35},
+ "children": [
+ {"name": "Paolo Dela Cruz", "gender": "male", "age": 12},
+ {"name": "Maria Dela Cruz", "gender": "female", "age": 8},
+ ],
"marital_status": "married",
"household_size": 4,
},
"journey": [
- {"action": "register", "days_back": 120},
+ {"action": "register_household", "days_back": 120},
+ {"action": "add_household_members", "days_back": 115},
{"action": "enroll_program", "program": "Cash Transfer Program", "days_back": 100},
{"action": "create_payment", "amount": 150, "status": "paid", "days_back": 70},
{"action": "create_payment", "amount": 150, "status": "failed", "days_back": 40},
@@ -177,7 +193,7 @@
"story_description": "Demonstrate targeting and multi-program enrollment",
"profile": {
"gender": "female",
- "age": 67,
+ "age": 72,
"education": "none",
"marital_status": "widowed",
"household_size": 1,
@@ -190,17 +206,17 @@
{"action": "vulnerability_assessment", "score": "high", "days_back": 195},
{
"action": "verify_eligibility",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"cel_check": "age_retirement",
"days_back": 182,
},
- {"action": "enroll_program", "program": "Elderly Pension", "days_back": 180},
+ {"action": "enroll_program", "program": "Elderly Social Pension", "days_back": 180},
{"action": "enroll_program", "program": "Food Assistance", "days_back": 175},
{
"action": "create_payment",
"amount": 100,
"status": "paid",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"days_back": 150,
},
{"action": "create_in_kind", "item": "Food Basket", "days_back": 150},
@@ -208,21 +224,21 @@
"action": "create_payment",
"amount": 100,
"status": "paid",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"days_back": 120,
},
{
"action": "create_payment",
"amount": 100,
"status": "paid",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"days_back": 90,
},
{
"action": "create_payment",
"amount": 100,
"status": "paid",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"days_back": 60,
},
],
@@ -235,75 +251,56 @@
{
"id": "pedro_reyes",
"name": "Pedro Reyes",
- "type": "farmer",
- "story_title": "Cooperative Leader",
- "story_description": "Demonstrate farmer registry depth and group features",
+ "type": "individual",
+ "story_title": "Community Leader",
+ "story_description": "Demonstrate community engagement and extension visits",
"profile": {
"gender": "male",
"age": 55,
"education": "tertiary",
- "farm_size": 8.0,
- "farm_size_hectares": 8.0, # CEL: Large livestock farm
- "farm_type": "livestock",
- "main_livestock": "dairy",
"district": "Central District",
"marital_status": "married",
"household_size": 6,
- "role": "cooperative_chairman",
+ "role": "community_leader",
},
"journey": [
{"action": "register", "days_back": 365},
- {"action": "add_farm_details", "comprehensive": True, "days_back": 360},
- {"action": "register_cooperative_leader", "days_back": 350},
- {"action": "enroll_program", "program": "Livestock Improvement Program", "days_back": 300},
{"action": "create_event", "event_type": "extension_visit", "days_back": 250},
{"action": "create_event", "event_type": "extension_visit", "days_back": 200},
- {"action": "create_in_kind", "item": "Improved Breed Cattle", "quantity": 2, "days_back": 150},
],
"demo_points": [
- "Detailed farm profile (livestock, assets, land records)",
- "Group/cooperative membership",
+ "Community leadership role",
"Extension service history",
- "In-kind entitlement",
],
},
{
"id": "ana_mendoza",
"name": "Ana Mendoza",
- "type": "farmer",
- "story_title": "Young Modern Farmer",
- "story_description": "Demonstrate tech-savvy farmer profile",
+ "type": "individual",
+ "story_title": "Young Registrant",
+ "story_description": "Demonstrate digital registration and verification",
"profile": {
"gender": "female",
"age": 28,
"education": "university",
- "farm_size": 3.0,
- "farm_size_hectares": 3.0, # CEL: Youth farmer eligibility
- "farm_type": "crop",
- "main_crop": "mixed_vegetables",
"district": "Eastern District",
"marital_status": "single",
"household_size": 2,
"registration_channel": "mobile_app",
- "employment_status": "self_employed", # CEL: Youth employment status
+ "employment_status": "self_employed",
},
"journey": [
{"action": "register", "channel": "mobile_app", "days_back": 90},
- {"action": "add_farm_details", "with_gps": True, "days_back": 85},
- {"action": "apply_program", "program": "Input Subsidy Program", "days_back": 80},
{"action": "verify_eligibility", "days_back": 75},
- {"action": "enroll_program", "program": "Input Subsidy Program", "days_back": 70},
- {"action": "create_in_kind", "item": "Inputs Package", "days_back": 45},
],
"demo_points": [
- "Modern farmer profile",
- "GPS coordinates on farm",
"Digital registration pathway",
+ "Verification workflow",
],
},
{
"id": "carlos_elena_morales",
- "name": "Carlos Morales",
+ "name": "Morales",
"type": "household",
"story_title": "Household Unit",
"story_description": "Demonstrate household/group registration",
@@ -315,8 +312,6 @@
{"name": "Sofia Morales", "gender": "female", "age": 12},
{"name": "Luis Morales", "gender": "male", "age": 8},
],
- "farm_size": 2.0,
- "farm_size_hectares": 2.0, # CEL: Household farm size
"child_count": 3, # CEL: Child benefit eligibility
"district": "Southern District",
},
@@ -325,13 +320,13 @@
{"action": "add_household_members", "days_back": 145},
{
"action": "verify_eligibility",
- "program": "Child Support Grant",
+ "program": "Universal Child Grant",
"cel_check": "member_count",
"days_back": 142,
},
- {"action": "enroll_program", "program": "Child Support Grant", "days_back": 140},
- {"action": "create_payment", "amount": 300, "status": "paid", "days_back": 100},
- {"action": "create_payment", "amount": 300, "status": "paid", "days_back": 10},
+ {"action": "enroll_program", "program": "Universal Child Grant", "days_back": 140},
+ {"action": "create_payment", "amount": 150, "status": "paid", "days_back": 100},
+ {"action": "create_payment", "amount": 150, "status": "paid", "days_back": 10},
],
"demo_points": [
"Household with multiple members",
@@ -341,16 +336,16 @@
},
{
"id": "amina_osman_household",
- "name": "Amina Osman",
+ "name": "Aquino",
"type": "household",
"story_title": "Single-Parent Household",
"story_description": "Widowed mother with children - vulnerable household",
"profile": {
- "head": {"name": "Amina Osman", "gender": "female", "age": 38},
+ "head": {"name": "Rosario Aquino", "gender": "female", "age": 38},
"children": [
- {"name": "Yusuf Osman", "gender": "male", "age": 15},
- {"name": "Layla Osman", "gender": "female", "age": 11},
- {"name": "Hassan Osman", "gender": "male", "age": 7},
+ {"name": "Daniel Aquino", "gender": "male", "age": 15},
+ {"name": "Angela Aquino", "gender": "female", "age": 11},
+ {"name": "Rafael Aquino", "gender": "male", "age": 7},
],
"marital_status": "widowed",
"vulnerability": ["single_parent", "low_income", "female_headed"],
@@ -363,14 +358,14 @@
{"action": "vulnerability_assessment", "score": "high", "days_back": 175},
{
"action": "verify_eligibility",
- "program": "Child Support Grant",
+ "program": "Universal Child Grant",
"cel_check": "member_count",
"days_back": 162,
},
- {"action": "enroll_program", "program": "Child Support Grant", "days_back": 160},
+ {"action": "enroll_program", "program": "Universal Child Grant", "days_back": 160},
{"action": "enroll_program", "program": "Food Assistance", "days_back": 155},
- {"action": "create_payment", "amount": 350, "status": "paid", "days_back": 120},
- {"action": "create_payment", "amount": 350, "status": "paid", "days_back": 60},
+ {"action": "create_payment", "amount": 150, "status": "paid", "days_back": 120},
+ {"action": "create_payment", "amount": 150, "status": "paid", "days_back": 60},
],
"demo_points": [
"Female-headed household",
@@ -381,7 +376,7 @@
},
{
"id": "jose_reyes_multigenerational",
- "name": "Jose Reyes Sr",
+ "name": "Reyes",
"type": "household",
"story_title": "Multi-Generational Household",
"story_description": "Three generations living together - grandparents, parents, children",
@@ -398,8 +393,6 @@
{"name": "Antonio Reyes", "gender": "male", "age": 10},
{"name": "Isabella Reyes", "gender": "female", "age": 6},
],
- "farm_size": 5.0,
- "farm_size_hectares": 5.0, # CEL: Multi-generational household farm
"child_count": 3, # CEL: Children under 18 (excluding 18-year-old)
"district": "Northern District",
"vulnerability": ["elderly_members"],
@@ -409,44 +402,44 @@
{"action": "add_household_members", "days_back": 360},
{
"action": "verify_eligibility",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"cel_check": "age_retirement",
"days_back": 352,
},
- {"action": "enroll_program", "program": "Elderly Pension", "days_back": 350},
+ {"action": "enroll_program", "program": "Elderly Social Pension", "days_back": 350},
{
"action": "verify_eligibility",
- "program": "Child Support Grant",
+ "program": "Universal Child Grant",
"cel_check": "member_count",
"days_back": 342,
},
- {"action": "enroll_program", "program": "Child Support Grant", "days_back": 340},
+ {"action": "enroll_program", "program": "Universal Child Grant", "days_back": 340},
{
"action": "create_payment",
"amount": 200,
"status": "paid",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"days_back": 300,
},
{
"action": "create_payment",
"amount": 400,
"status": "paid",
- "program": "Child Support Grant",
+ "program": "Universal Child Grant",
"days_back": 290,
},
{
"action": "create_payment",
"amount": 200,
"status": "paid",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"days_back": 240,
},
{
"action": "create_payment",
"amount": 200,
"status": "paid",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"days_back": 180,
},
],
@@ -459,65 +452,51 @@
},
{
"id": "chen_large_family",
- "name": "Chen Wei",
+ "name": "Bautista",
"type": "household",
"story_title": "Large Family",
"story_description": "Large family with many children - demonstrates scale",
"profile": {
- "head": {"name": "Chen Wei", "gender": "male", "age": 48},
- "spouse": {"name": "Chen Mei", "gender": "female", "age": 44},
+ "head": {"name": "Eduardo Bautista", "gender": "male", "age": 48},
+ "spouse": {"name": "Carmen Bautista", "gender": "female", "age": 44},
"children": [
- {"name": "Chen Ling", "gender": "female", "age": 22},
- {"name": "Chen Jun", "gender": "male", "age": 19},
- {"name": "Chen Xiao", "gender": "female", "age": 16},
- {"name": "Chen Yan", "gender": "female", "age": 13},
- {"name": "Chen Bo", "gender": "male", "age": 9},
+ {"name": "Patricia Bautista", "gender": "female", "age": 22},
+ {"name": "Fernando Bautista", "gender": "male", "age": 19},
+ {"name": "Lucia Bautista", "gender": "female", "age": 16},
+ {"name": "Rosalie Bautista", "gender": "female", "age": 13},
+ {"name": "Antonio Bautista", "gender": "male", "age": 9},
],
- "farm_size": 4.5,
- "farm_size_hectares": 4.5, # CEL: Large family farm
- "child_count": 3, # CEL: Children under 18 (Xiao, Yan, Bo)
- "farm_type": "crop",
- "main_crop": "rice",
+ "child_count": 3, # CEL: Children under 18 (Lucia, Rosalie, Antonio)
"district": "Eastern District",
},
"journey": [
{"action": "register_household", "days_back": 200},
{"action": "add_household_members", "days_back": 195},
- {"action": "add_farm_details", "days_back": 190},
{
"action": "verify_eligibility",
- "program": "Input Subsidy Program",
- "cel_check": "farm_size",
- "days_back": 182,
- },
- {"action": "enroll_program", "program": "Input Subsidy Program", "days_back": 180},
- {
- "action": "verify_eligibility",
- "program": "Child Support Grant",
+ "program": "Universal Child Grant",
"cel_check": "member_count",
"days_back": 177,
},
- {"action": "enroll_program", "program": "Child Support Grant", "days_back": 175},
- {"action": "create_in_kind", "item": "Inputs Package", "days_back": 150},
- {"action": "create_payment", "amount": 450, "status": "paid", "days_back": 140},
- {"action": "create_payment", "amount": 450, "status": "paid", "days_back": 80},
+ {"action": "enroll_program", "program": "Universal Child Grant", "days_back": 175},
+ {"action": "create_payment", "amount": 150, "status": "paid", "days_back": 140},
+ {"action": "create_payment", "amount": 150, "status": "paid", "days_back": 80},
],
"demo_points": [
"Large family (7 members)",
"Mixed age children (some eligible, some not)",
- "Farming household",
- "Both in-kind and cash benefits",
+ "Cash benefits for children",
],
},
{
"id": "manuel_gloria_elderly",
- "name": "Manuel Santos",
+ "name": "Pangilinan",
"type": "household",
"story_title": "Elderly Couple",
"story_description": "Elderly couple without dependents",
"profile": {
- "head": {"name": "Manuel Santos", "gender": "male", "age": 75},
- "spouse": {"name": "Gloria Santos", "gender": "female", "age": 71},
+ "head": {"name": "Manuel Pangilinan", "gender": "male", "age": 75},
+ "spouse": {"name": "Gloria Pangilinan", "gender": "female", "age": 71},
"vulnerability": ["elderly", "health_issues", "limited_mobility"],
"vulnerability_score": 70, # CEL: Elderly couple vulnerability
"has_formal_pension": False, # CEL: Elderly pension eligibility
@@ -528,11 +507,11 @@
{"action": "vulnerability_assessment", "score": "medium", "days_back": 245},
{
"action": "verify_eligibility",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"cel_check": "age_retirement",
"days_back": 232,
},
- {"action": "enroll_program", "program": "Elderly Pension", "days_back": 230},
+ {"action": "enroll_program", "program": "Elderly Social Pension", "days_back": 230},
{"action": "enroll_program", "program": "Food Assistance", "days_back": 220},
{"action": "create_payment", "amount": 200, "status": "paid", "days_back": 200},
{"action": "create_in_kind", "item": "Food Basket", "days_back": 195},
@@ -549,31 +528,28 @@
},
{
"id": "nguyen_extended_family",
- "name": "James Nguyen",
+ "name": "Navarro",
"type": "household",
"story_title": "Extended Family",
"story_description": "Siblings and their families living together",
"profile": {
- "head": {"name": "James Nguyen", "gender": "male", "age": 52},
+ "head": {"name": "Ricardo Navarro", "gender": "male", "age": 52},
"adults": [
- {"name": "Linda Nguyen", "gender": "female", "age": 48, "relation": "spouse"},
+ {"name": "Lourdes Navarro", "gender": "female", "age": 48, "relation": "spouse"},
{
- "name": "Michael Nguyen",
+ "name": "Eduardo Navarro",
"gender": "male",
"age": 46,
"relation": "brother",
"disability_status": "disabled",
},
- {"name": "Sarah Nguyen", "gender": "female", "age": 44, "relation": "sister-in-law"},
+ {"name": "Cristina Navarro", "gender": "female", "age": 44, "relation": "sister-in-law"},
],
- "farm_size": 6.0,
- "farm_size_hectares": 6.0, # CEL: Extended family farm
- "farm_type": "mixed",
"district": "Southern District",
"vulnerability": ["disability"],
"vulnerability_score": 65, # CEL: Disability in household
"disabled_count": 1, # CEL: Member with disability
- "notes": "Brother Michael has disability requiring care",
+ "notes": "Brother Eduardo has disability requiring care",
},
"journey": [
{"action": "register_household", "days_back": 300},
@@ -594,34 +570,40 @@
},
{
"id": "ibrahim_hassan",
- "name": "Ibrahim Hassan",
- "type": "individual",
- "story_title": "Displaced Farmer",
+ "name": "Gutierrez",
+ "type": "household",
+ "story_title": "Displaced Family",
"story_description": "Demonstrate emergency/vulnerability response",
"profile": {
- "gender": "male",
- "age": 50,
- "education": "primary",
+ "head": {"name": "Ramon Gutierrez", "gender": "male", "age": 50},
+ "spouse": {"name": "Elena Gutierrez", "gender": "female", "age": 45},
+ "children": [
+ {"name": "Marco Gutierrez", "gender": "male", "age": 18},
+ {"name": "Isabella Gutierrez", "gender": "female", "age": 15},
+ {"name": "Jose Gutierrez", "gender": "male", "age": 12},
+ {"name": "Sofia Gutierrez", "gender": "female", "age": 9},
+ {"name": "Miguel Gutierrez", "gender": "male", "age": 5},
+ ],
"marital_status": "married",
"household_size": 7,
"status": "internally_displaced",
- "previous_farm_size": 5.0,
"vulnerability": ["displaced", "lost_assets"],
"vulnerability_score": 85, # CEL: Emergency relief - high vulnerability
"displacement_status": "displaced", # CEL: Emergency eligibility
},
"journey": [
- {"action": "emergency_register", "days_back": 60},
+ {"action": "register_household", "days_back": 60},
+ {"action": "add_household_members", "days_back": 59},
{"action": "vulnerability_assessment", "score": "very_high", "days_back": 58},
{
"action": "verify_eligibility",
- "program": "Emergency Cash Transfer",
+ "program": "Emergency Relief Fund",
"cel_check": "vulnerability_metric",
"days_back": 56,
},
- {"action": "enroll_program", "program": "Emergency Cash Transfer", "days_back": 55},
- {"action": "create_payment", "amount": 500, "status": "paid", "days_back": 50},
- {"action": "create_payment", "amount": 500, "status": "paid", "days_back": 35},
+ {"action": "enroll_program", "program": "Emergency Relief Fund", "days_back": 55},
+ {"action": "create_payment", "amount": 400, "status": "paid", "days_back": 50},
+ {"action": "create_payment", "amount": 400, "status": "paid", "days_back": 35},
{
"action": "create_grm_ticket",
"title": "Request for resettlement support",
@@ -639,7 +621,7 @@
},
{
"id": "fatima_al_rahman",
- "name": "Fatima Al-Rahman",
+ "name": "Teresa Villanueva",
"type": "individual",
"story_title": "Information Seeker",
"story_description": "Demonstrate GRM for inquiries (not complaints)",
@@ -670,7 +652,7 @@
},
{
"id": "david_sofia_martinez",
- "name": "David Martinez",
+ "name": "Martinez",
"type": "household",
"story_title": "Disability Support",
"story_description": "Household with disabled child - demonstrates disability assistance",
@@ -680,8 +662,6 @@
"children": [
{"name": "Miguel Martinez", "gender": "male", "age": 12, "disability_status": "disabled"},
],
- "farm_size": 1.5,
- "farm_size_hectares": 1.5, # CEL: Small farm household
"disabled_count": 1, # CEL: Disability Support Grant eligibility
"child_count": 1,
"district": "Western District",
@@ -715,42 +695,49 @@
{
"id": "luis_fernandez",
"name": "Luis Fernandez",
- "type": "farmer",
+ "type": "individual",
"story_title": "Pending Application",
"story_description": "Shows application pipeline",
- "profile": {"gender": "male", "age": 40, "farm_size": 1.5},
+ "profile": {"gender": "male", "age": 40},
"journey": [
{"action": "register", "days_back": 30},
- {"action": "apply_program", "program": "Input Subsidy Program", "status": "pending", "days_back": 25},
],
},
{
"id": "mary_johnson",
- "name": "Mary Johnson",
+ "name": "Lorna Pascual",
"type": "individual",
"story_title": "Rejected Application",
"story_description": "Shows eligibility rules",
- "profile": {"gender": "female", "age": 32},
+ "profile": {"gender": "female", "age": 55},
"journey": [
{"action": "register", "days_back": 60},
{
"action": "apply_program",
- "program": "Elderly Pension",
+ "program": "Elderly Social Pension",
"status": "rejected",
- "reason": "Age requirement not met",
+ "reason": "Age requirement not met (55 < 65)",
"days_back": 55,
},
],
},
{
"id": "ahmed_said",
- "name": "Ahmed Said",
- "type": "farmer",
+ "name": "Castillo",
+ "type": "household",
"story_title": "Multiple GRM Tickets",
"story_description": "Shows GRM history",
- "profile": {"gender": "male", "age": 45, "farm_size": 2.0},
+ "profile": {
+ "head": {"name": "Roberto Castillo", "gender": "male", "age": 45},
+ "spouse": {"name": "Linda Castillo", "gender": "female", "age": 40},
+ "children": [
+ {"name": "Paolo Castillo", "gender": "male", "age": 14},
+ ],
+ "household_size": 3,
+ },
"journey": [
- {"action": "register", "days_back": 200},
+ {"action": "register_household", "days_back": 200},
+ {"action": "add_household_members", "days_back": 195},
{"action": "enroll_program", "program": "Cash Transfer Program", "days_back": 180},
{"action": "create_grm_ticket", "title": "Ticket 1", "days_back": 150},
{"action": "create_grm_ticket", "title": "Ticket 2", "days_back": 100},
@@ -759,26 +746,24 @@
},
{
"id": "grace_okonkwo",
- "name": "Grace Okonkwo",
- "type": "farmer",
+ "name": "Maricel Ramos",
+ "type": "individual",
"story_title": "Recently Registered",
"story_description": "Shows new records",
- "profile": {"gender": "female", "age": 35, "farm_size": 1.0},
+ "profile": {"gender": "female", "age": 35},
"journey": [
{"action": "register", "days_back": 5},
],
},
{
"id": "david_kim",
- "name": "David Kim",
- "type": "farmer",
- "story_title": "Contract Farmer",
- "story_description": "Shows guaranteed market arrangement",
- "profile": {"gender": "male", "age": 48, "farm_size": 4.0, "contract_farming": True},
+ "name": "Eduardo Tan",
+ "type": "individual",
+ "story_title": "Long-term Registrant",
+ "story_description": "Shows long registration history",
+ "profile": {"gender": "male", "age": 48},
"journey": [
{"action": "register", "days_back": 300},
- {"action": "add_farm_details", "days_back": 295},
- {"action": "register_contract", "buyer": "AgriCorp Ltd", "days_back": 250},
],
},
]
@@ -816,15 +801,15 @@
},
{
"id": "tutorial_santos_family",
- "name": "Santos Family",
+ "name": "Tolentino Family",
"type": "household",
"story_title": "Tutorial: Eligible (Low Income + Child Under 5)",
"story_description": "Tutorial household meeting both criteria - ELIGIBLE",
"profile": {
- "head": {"name": "Jose Santos", "gender": "male", "age": 35, "income": 8000},
- "spouse": {"name": "Ana Santos", "gender": "female", "age": 32},
+ "head": {"name": "Jose Tolentino", "gender": "male", "age": 35, "income": 8000},
+ "spouse": {"name": "Ana Tolentino", "gender": "female", "age": 32},
"children": [
- {"name": "Mia Santos", "gender": "female", "age": 4}, # Born ~2021, under 5
+ {"name": "Mia Tolentino", "gender": "female", "age": 4}, # Born ~2021, under 5
],
"child_count": 1,
"district": "Northern District",
@@ -840,16 +825,16 @@
},
{
"id": "tutorial_cruz_family",
- "name": "Cruz Family",
+ "name": "Salazar Family",
"type": "household",
"story_title": "Tutorial: Not Eligible (Income Above Threshold)",
"story_description": "Tutorial household with income above threshold - NOT ELIGIBLE",
"profile": {
- "head": {"name": "Pedro Cruz", "gender": "male", "age": 45, "income": 12000},
- "spouse": {"name": "Teresa Cruz", "gender": "female", "age": 42},
+ "head": {"name": "Pedro Salazar", "gender": "male", "age": 45, "income": 12000},
+ "spouse": {"name": "Teresa Salazar", "gender": "female", "age": 42},
"children": [
- {"name": "Juan Cruz", "gender": "male", "age": 15},
- {"name": "Maria Cruz", "gender": "female", "age": 10},
+ {"name": "Juan Salazar", "gender": "male", "age": 15},
+ {"name": "Maria Salazar", "gender": "female", "age": 10},
],
"child_count": 2,
"district": "Eastern District",
@@ -866,15 +851,15 @@
},
{
"id": "tutorial_reyes_family",
- "name": "Reyes Family",
+ "name": "Mercado Family",
"type": "household",
"story_title": "Tutorial: Eligible (Low Income + Child Under 5)",
"story_description": "Tutorial household meeting both criteria - ELIGIBLE",
"profile": {
- "head": {"name": "Ramon Reyes", "gender": "male", "age": 30, "income": 6000},
- "spouse": {"name": "Elena Reyes", "gender": "female", "age": 28},
+ "head": {"name": "Ramon Mercado", "gender": "male", "age": 30, "income": 6000},
+ "spouse": {"name": "Elena Mercado", "gender": "female", "age": 28},
"children": [
- {"name": "Lucia Reyes", "gender": "female", "age": 2}, # Born ~2023, under 5
+ {"name": "Lucia Mercado", "gender": "female", "age": 2}, # Born ~2023, under 5
],
"child_count": 1,
"district": "Southern District",
@@ -931,13 +916,28 @@
# -----------------------------------------------------------------------
"si_LK": {
# DEMO_STORIES
- "maria_santos": {"name": "Kumari Perera"},
- "juan_dela_cruz": {"name": "Nimal Bandara"},
+ "maria_santos": {
+ "name": "Perera",
+ "profile_names": {
+ "head": "Kumari Perera",
+ "spouse": "Sunil Perera",
+ "children": ["Nimali Perera", "Kasun Perera"],
+ "adults": ["Padma Perera"],
+ },
+ },
+ "juan_dela_cruz": {
+ "name": "Bandara",
+ "profile_names": {
+ "head": "Nimal Bandara",
+ "spouse": "Kamani Bandara",
+ "children": ["Lahiru Bandara", "Sanduni Bandara"],
+ },
+ },
"rosa_garcia": {"name": "Malini Silva"},
"pedro_reyes": {"name": "Saman Jayawardena"},
"ana_mendoza": {"name": "Sachini Dissanayake"},
"carlos_elena_morales": {
- "name": "Kasun Fernando",
+ "name": "Fernando",
"profile_names": {
"head": "Kasun Fernando",
"spouse": "Dilani Fernando",
@@ -945,14 +945,14 @@
},
},
"amina_osman_household": {
- "name": "Anoma Herath",
+ "name": "Herath",
"profile_names": {
"head": "Anoma Herath",
"children": ["Lahiru Herath", "Hiruni Herath", "Dinesh Herath"],
},
},
"jose_reyes_multigenerational": {
- "name": "Kamal Rathnayake",
+ "name": "Rathnayake",
"profile_names": {
"head": "Kamal Rathnayake",
"spouse": "Ramya Rathnayake",
@@ -966,7 +966,7 @@
},
},
"chen_large_family": {
- "name": "Thilak Gunasekara",
+ "name": "Gunasekara",
"profile_names": {
"head": "Thilak Gunasekara",
"spouse": "Kusum Gunasekara",
@@ -980,14 +980,14 @@
},
},
"manuel_gloria_elderly": {
- "name": "Sunil Wijesinghe",
+ "name": "Wijesinghe",
"profile_names": {
"head": "Sunil Wijesinghe",
"spouse": "Sirima Wijesinghe",
},
},
"nguyen_extended_family": {
- "name": "Ranjith Amarasinghe",
+ "name": "Amarasinghe",
"profile_names": {
"head": "Ranjith Amarasinghe",
"adults": [
@@ -997,10 +997,23 @@
],
},
},
- "ibrahim_hassan": {"name": "Asanka Kumara"},
+ "ibrahim_hassan": {
+ "name": "Kumara",
+ "profile_names": {
+ "head": "Asanka Kumara",
+ "spouse": "Chamari Kumara",
+ "children": [
+ "Dinesh Kumara",
+ "Nishadi Kumara",
+ "Tharindu Kumara",
+ "Dilhani Kumara",
+ "Ravindu Kumara",
+ ],
+ },
+ },
"fatima_al_rahman": {"name": "Ishara Senanayake"},
"david_sofia_martinez": {
- "name": "Sanjeewa Wickramasinghe",
+ "name": "Wickramasinghe",
"profile_names": {
"head": "Sanjeewa Wickramasinghe",
"spouse": "Nisansala Wickramasinghe",
@@ -1010,7 +1023,14 @@
# BACKGROUND_STORIES
"luis_fernandez": {"name": "Dinesh Rajapaksa"},
"mary_johnson": {"name": "Priyanka Mendis"},
- "ahmed_said": {"name": "Ruwan Weerasinghe"},
+ "ahmed_said": {
+ "name": "Weerasinghe",
+ "profile_names": {
+ "head": "Ruwan Weerasinghe",
+ "spouse": "Nilmini Weerasinghe",
+ "children": ["Sampath Weerasinghe"],
+ },
+ },
"grace_okonkwo": {"name": "Sanduni Karunaratne"},
"david_kim": {"name": "Mahesh Gamage"},
# TUTORIAL_STORIES
@@ -1060,13 +1080,28 @@
# -----------------------------------------------------------------------
"fr_TG": {
# DEMO_STORIES
- "maria_santos": {"name": "Ama Koffi"},
- "juan_dela_cruz": {"name": "Kofi Mensah"},
+ "maria_santos": {
+ "name": "Koffi",
+ "profile_names": {
+ "head": "Ama Koffi",
+ "spouse": "Kokou Koffi",
+ "children": ["Esi Koffi", "Kweku Koffi"],
+ "adults": ["Adjo Koffi"],
+ },
+ },
+ "juan_dela_cruz": {
+ "name": "Mensah",
+ "profile_names": {
+ "head": "Kofi Mensah",
+ "spouse": "Akosua Mensah",
+ "children": ["Yao Mensah", "Ama Mensah"],
+ },
+ },
"rosa_garcia": {"name": "Adzo Amegah"},
"pedro_reyes": {"name": "Yao Dossou"},
"ana_mendoza": {"name": "Akua Ayivi"},
"carlos_elena_morales": {
- "name": "Kodjo Agbeko",
+ "name": "Agbeko",
"profile_names": {
"head": "Kodjo Agbeko",
"spouse": "Esi Agbeko",
@@ -1074,14 +1109,14 @@
},
},
"amina_osman_household": {
- "name": "Adjoa Tetteh",
+ "name": "Tetteh",
"profile_names": {
"head": "Adjoa Tetteh",
"children": ["Messan Tetteh", "Akossiwa Tetteh", "Edem Tetteh"],
},
},
"jose_reyes_multigenerational": {
- "name": "Kwame Lawson",
+ "name": "Lawson",
"profile_names": {
"head": "Kwame Lawson",
"spouse": "Afia Lawson",
@@ -1095,7 +1130,7 @@
},
},
"chen_large_family": {
- "name": "Mawuli Akakpo",
+ "name": "Akakpo",
"profile_names": {
"head": "Mawuli Akakpo",
"spouse": "Kafui Akakpo",
@@ -1109,23 +1144,36 @@
},
},
"manuel_gloria_elderly": {
- "name": "Atsu Amouzou",
+ "name": "Amouzou",
"profile_names": {
"head": "Atsu Amouzou",
"spouse": "Akpene Amouzou",
},
},
"nguyen_extended_family": {
- "name": "Selom Gbeho",
+ "name": "Gbeho",
"profile_names": {
"head": "Selom Gbeho",
"adults": ["Mawusi Gbeho", "Senyo Gbeho", "Ayele Gbeho"],
},
},
- "ibrahim_hassan": {"name": "Kosi Deku"},
+ "ibrahim_hassan": {
+ "name": "Deku",
+ "profile_names": {
+ "head": "Kosi Deku",
+ "spouse": "Akua Deku",
+ "children": [
+ "Komla Deku",
+ "Ablavi Deku",
+ "Kofi Deku",
+ "Ama Deku",
+ "Edem Deku",
+ ],
+ },
+ },
"fatima_al_rahman": {"name": "Afia Sossou"},
"david_sofia_martinez": {
- "name": "Ata Koudawo",
+ "name": "Koudawo",
"profile_names": {
"head": "Ata Koudawo",
"spouse": "Ama Koudawo",
@@ -1135,7 +1183,14 @@
# BACKGROUND_STORIES
"luis_fernandez": {"name": "Messan Ameganvi"},
"mary_johnson": {"name": "Ablavi Gbeassor"},
- "ahmed_said": {"name": "Komla Agbodjan"},
+ "ahmed_said": {
+ "name": "Agbodjan",
+ "profile_names": {
+ "head": "Komla Agbodjan",
+ "spouse": "Adjoa Agbodjan",
+ "children": ["Messan Agbodjan"],
+ },
+ },
"grace_okonkwo": {"name": "Akossiwa Adjakly"},
"david_kim": {"name": "Yaovi Assignon"},
# TUTORIAL_STORIES
diff --git a/spp_demo/tests/test_demo_stories.py b/spp_demo/tests/test_demo_stories.py
index 12de6876..273bc360 100644
--- a/spp_demo/tests/test_demo_stories.py
+++ b/spp_demo/tests/test_demo_stories.py
@@ -69,7 +69,7 @@ def test_03_get_story_by_id(self):
maria = demo_stories.get_story_by_id("maria_santos")
self.assertIsNotNone(maria)
- self.assertEqual(maria["name"], "Maria Santos")
+ self.assertEqual(maria["name"], "Santos")
nonexistent = demo_stories.get_story_by_id("nonexistent_story")
self.assertIsNone(nonexistent)
@@ -78,7 +78,7 @@ def test_04_get_story_by_name(self):
"""Test get_story_by_name function."""
from odoo.addons.spp_demo.models import demo_stories
- juan = demo_stories.get_story_by_name("Juan Dela Cruz")
+ juan = demo_stories.get_story_by_name("Dela Cruz")
self.assertIsNotNone(juan)
self.assertEqual(juan["id"], "juan_dela_cruz")
@@ -222,9 +222,7 @@ def test_11_generate_stories_idempotent(self):
self.assertEqual(len(created1), len(created2))
# Check no duplicates
- maria_count = self.env["res.partner"].search_count(
- [("name", "=", "Maria Santos"), ("is_registrant", "=", True)]
- )
+ maria_count = self.env["res.partner"].search_count([("name", "=", "Santos"), ("is_registrant", "=", True)])
self.assertEqual(maria_count, 1)
def test_12_maria_santos_story(self):
@@ -239,7 +237,7 @@ def test_12_maria_santos_story(self):
generator.generate_stories()
maria = self.env["res.partner"].search(
- [("name", "=", "Maria Santos"), ("is_registrant", "=", True)],
+ [("name", "=", "Santos"), ("is_registrant", "=", True)],
limit=1,
)
@@ -258,9 +256,9 @@ def test_13_household_story_with_members(self):
generator.generate_stories()
- # Carlos Morales is a household head
+ # Morales is a household group (Carlos Morales is head)
carlos = self.env["res.partner"].search(
- [("name", "=", "Carlos Morales"), ("is_registrant", "=", True)],
+ [("name", "=", "Morales"), ("is_registrant", "=", True)],
limit=1,
)
@@ -308,7 +306,7 @@ def test_15_story_registration_dates(self):
generator.generate_stories()
maria = self.env["res.partner"].search(
- [("name", "=", "Maria Santos"), ("is_registrant", "=", True)],
+ [("name", "=", "Santos"), ("is_registrant", "=", True)],
limit=1,
)
@@ -335,8 +333,8 @@ def test_16_get_localized_stories_default(self):
self.assertEqual(len(stories_ph), len(all_stories))
# Names should be unchanged
- self.assertEqual(stories_none[0]["name"], "Maria Santos")
- self.assertEqual(stories_ph[0]["name"], "Maria Santos")
+ self.assertEqual(stories_none[0]["name"], "Santos")
+ self.assertEqual(stories_ph[0]["name"], "Santos")
def test_17_get_localized_stories_sri_lanka(self):
"""Test that si_LK locale returns Sinhalese names."""
@@ -348,11 +346,11 @@ def test_17_get_localized_stories_sri_lanka(self):
# Find maria_santos story
maria = next(s for s in stories if s["id"] == "maria_santos")
- self.assertEqual(maria["name"], "Kumari Perera")
+ self.assertEqual(maria["name"], "Perera")
# Find household story (carlos_elena_morales)
carlos = next(s for s in stories if s["id"] == "carlos_elena_morales")
- self.assertEqual(carlos["name"], "Kasun Fernando")
+ self.assertEqual(carlos["name"], "Fernando")
self.assertEqual(carlos["profile"]["head"]["name"], "Kasun Fernando")
self.assertEqual(carlos["profile"]["spouse"]["name"], "Dilani Fernando")
self.assertEqual(carlos["profile"]["children"][0]["name"], "Nuwan Fernando")
@@ -364,10 +362,10 @@ def test_18_get_localized_stories_togo(self):
stories = demo_stories.get_localized_stories("fr_TG")
maria = next(s for s in stories if s["id"] == "maria_santos")
- self.assertEqual(maria["name"], "Ama Koffi")
+ self.assertEqual(maria["name"], "Koffi")
carlos = next(s for s in stories if s["id"] == "carlos_elena_morales")
- self.assertEqual(carlos["name"], "Kodjo Agbeko")
+ self.assertEqual(carlos["name"], "Agbeko")
self.assertEqual(carlos["profile"]["head"]["name"], "Kodjo Agbeko")
self.assertEqual(carlos["profile"]["spouse"]["name"], "Esi Agbeko")
@@ -383,27 +381,27 @@ def test_19_get_localized_reserved_names(self):
self.assertEqual(ph_names, demo_stories.RESERVED_NAMES)
# si_LK should have Sinhalese names
- self.assertIn("Kumari Perera", lk_names)
- self.assertNotIn("Maria Santos", lk_names)
+ self.assertIn("Perera", lk_names)
+ self.assertNotIn("Santos", lk_names)
# fr_TG should have Togolese names
- self.assertIn("Ama Koffi", tg_names)
- self.assertNotIn("Maria Santos", tg_names)
+ self.assertIn("Koffi", tg_names)
+ self.assertNotIn("Santos", tg_names)
def test_20_get_localized_name(self):
"""Test single name lookup by story ID and locale."""
from odoo.addons.spp_demo.models import demo_stories
# Default/Filipino
- self.assertEqual(demo_stories.get_localized_name("maria_santos"), "Maria Santos")
- self.assertEqual(demo_stories.get_localized_name("maria_santos", "fil_PH"), "Maria Santos")
+ self.assertEqual(demo_stories.get_localized_name("maria_santos"), "Santos")
+ self.assertEqual(demo_stories.get_localized_name("maria_santos", "fil_PH"), "Santos")
# Sri Lanka
- self.assertEqual(demo_stories.get_localized_name("maria_santos", "si_LK"), "Kumari Perera")
- self.assertEqual(demo_stories.get_localized_name("juan_dela_cruz", "si_LK"), "Nimal Bandara")
+ self.assertEqual(demo_stories.get_localized_name("maria_santos", "si_LK"), "Perera")
+ self.assertEqual(demo_stories.get_localized_name("juan_dela_cruz", "si_LK"), "Bandara")
# Togo
- self.assertEqual(demo_stories.get_localized_name("maria_santos", "fr_TG"), "Ama Koffi")
+ self.assertEqual(demo_stories.get_localized_name("maria_santos", "fr_TG"), "Koffi")
# Unknown story ID falls back to None
self.assertIsNone(demo_stories.get_localized_name("nonexistent", "si_LK"))
@@ -431,8 +429,15 @@ def test_22_localized_stories_preserve_structure(self):
# Journey, profile (except names), demo_points should be unchanged
self.assertEqual(lk_maria["journey"], orig_maria["journey"])
- self.assertEqual(lk_maria["profile"]["age"], orig_maria["profile"]["age"])
- self.assertEqual(lk_maria["profile"]["gender"], orig_maria["profile"]["gender"])
+ # maria_santos is a household - check head's age/gender are preserved
+ self.assertEqual(
+ lk_maria["profile"]["head"]["age"],
+ orig_maria["profile"]["head"]["age"],
+ )
+ self.assertEqual(
+ lk_maria["profile"]["head"]["gender"],
+ orig_maria["profile"]["head"]["gender"],
+ )
def test_23_localized_stories_no_name_collisions(self):
"""Test that localized names don't collide within a locale."""
diff --git a/spp_demo/views/res_config_view.xml b/spp_demo/views/res_config_view.xml
index 814d7b2a..e1258ca0 100644
--- a/spp_demo/views/res_config_view.xml
+++ b/spp_demo/views/res_config_view.xml
@@ -11,6 +11,7 @@
string="SPP Demo Data Generator Settings"
name="spp_demo_data_generator_settings"
logo="/spp_demo/static/description/icon.png"
+ invisible="1"
>
Demo Data > Load Farmer Registry Demo**
-2. The wizard opens with options for demo mode (Sales, Training,
- Testing, Complete)
-3. Click "Load Demo Data" to generate
+1. Navigate to **Settings > Demo Data > Load Farmer Demo**
+2. Click "Load Demo Data" to generate
+
+Demo Programs
+~~~~~~~~~~~~~
+
+All programs use CEL expressions with activated registry variables:
+
+- **Input Subsidy Program**: Per-hectare scaling for smallholders with
+ productive land
+- **Equipment Grant Program**: Experience-based grant for farmers with
+ 2+ years
+- **Livestock Support Program**: Per-head scaling for livestock owners
+- **Climate Resilience Program**: Targets farms with idle land
+- **Aquaculture Support Program**: Activity-specific support for
+ aquaculture farmers
+
+Seeded Volume Generation
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``SeededFarmGenerator`` uses ``random.Random(seed=42)`` for all
+structural choices:
+
+- Farm names from Filipino last name pools
+- Member names (given + family) from locale-specific pools
+- Farm sizes, experience years, GPS coordinates
+- Activity quantities (crop areas, livestock counts, aquaculture)
+
+Running the generator twice with the same seed produces identical
+output.
+
+Security
+~~~~~~~~
+
+================================ =========
+Group Access
+================================ =========
+``spp_security.group_spp_admin`` Full CRUD
+================================ =========
Dependencies
~~~~~~~~~~~~
diff --git a/spp_farmer_registry_demo/readme/DESCRIPTION.md b/spp_farmer_registry_demo/readme/DESCRIPTION.md
index ee3986f8..60acf17b 100644
--- a/spp_farmer_registry_demo/readme/DESCRIPTION.md
+++ b/spp_farmer_registry_demo/readme/DESCRIPTION.md
@@ -1,29 +1,56 @@
-Demo data generator for the OpenSPP Farmer Registry. Creates 8 named farmer personas with complete Philippine farm profiles, 5 agricultural subsidy programs with CEL eligibility expressions, and optionally generates ~730 volume farms from deterministic blueprints using a seeded random generator (seed=42) for reproducible output.
+Demo data generator for the OpenSPP Farmer Registry. Creates 8 named farmer personas with complete Philippine farm profiles, 5 agricultural subsidy programs with CEL eligibility expressions, and optionally generates ~730 volume farms from deterministic blueprints using a seeded random generator (`seed=42`) for reproducible output.
### Key Capabilities
-- Generate 8 fixed story farms with complete profiles (Maria Santos, Juan Dela Cruz, Rosa Garcia, Amir Mangudadatu, Sofia Martinez, Ramon dela Cruz, Sittie Pangandaman, Danilo Villanueva)
-- Create 5 demo programs (Input Subsidy, Equipment Grant, Livestock Support, Climate Resilience, Aquaculture Support) with CEL-based eligibility
-- Generate ~730 deterministic volume farms from 21 blueprints via seeded random generator (seed=42)
-- Create 2 farm cooperatives (Nueva Ecija Rice Cooperative, BARMM Farmers Federation) demonstrating group-of-groups hierarchy
-- Include 3 edge case personas for testing eligibility boundaries (large commercial, idle land, new farmer)
-- Install Logic Packs with CEL expressions for eligibility and benefit calculations
-- Distribute farms geographically across 8 Philippine provinces with GPS coordinates
+- **8 Fixed Story Farms** with hardcoded profiles (Maria Santos, Juan Dela Cruz, Rosa Garcia, Amir Mangudadatu, Sofia Martinez, Ramon dela Cruz, Sittie Pangandaman, Danilo Villanueva)
+- **~730 Volume Farms** generated deterministically from 21 blueprints via `SeededFarmGenerator` with `random.Random(seed=42)` — same seed always produces identical farms, members, and activities
+- **5 Demo Programs** with CEL-based eligibility and benefit formulas (Input Subsidy, Equipment Grant, Livestock Support, Climate Resilience, Aquaculture Support)
+- **2 Farm Cooperatives** demonstrating group-of-groups hierarchy (Nueva Ecija Rice Cooperative, BARMM Farmers Federation)
+- **3 Edge Case Personas** for testing eligibility boundaries (AgriCorp Holdings, Idle Land Farm, New Farmer)
+- Install Logic Packs from `spp_studio` for eligibility rules
+- Create program cycles with entitlements and payments
+- Create change requests at various workflow stages
+- Geographic distribution across 8 Philippine provinces with GPS coordinates and land parcel polygons
### Key Models
-| Model | Description |
-| --- | --- |
-| `spp.farmer.demo.generator` | Core demo generator with all generation logic |
-| `spp.farmer.demo.wizard` | Wizard interface (inherits from generator) |
+| Model | Description |
+| ------------------------------ | --------------------------------------------------- |
+| `spp.farmer.demo.generator` | Core demo generator wizard with all generation logic |
### Configuration
After installing:
-1. Navigate to **Settings > Demo Data > Load Farmer Registry Demo**
-2. The wizard opens with options for demo mode (Sales, Training, Testing, Complete)
-3. Click "Load Demo Data" to generate
+1. Navigate to **Settings > Demo Data > Load Farmer Demo**
+2. Click "Load Demo Data" to generate
+
+### Demo Programs
+
+All programs use CEL expressions with activated registry variables:
+
+- **Input Subsidy Program**: Per-hectare scaling for smallholders with productive land
+- **Equipment Grant Program**: Experience-based grant for farmers with 2+ years
+- **Livestock Support Program**: Per-head scaling for livestock owners
+- **Climate Resilience Program**: Targets farms with idle land
+- **Aquaculture Support Program**: Activity-specific support for aquaculture farmers
+
+### Seeded Volume Generation
+
+The `SeededFarmGenerator` uses `random.Random(seed=42)` for all structural choices:
+
+- Farm names from Filipino last name pools
+- Member names (given + family) from locale-specific pools
+- Farm sizes, experience years, GPS coordinates
+- Activity quantities (crop areas, livestock counts, aquaculture)
+
+Running the generator twice with the same seed produces identical output.
+
+### Security
+
+| Group | Access |
+| ------------------------------ | --------- |
+| `spp_security.group_spp_admin` | Full CRUD |
### Dependencies
diff --git a/spp_farmer_registry_demo/static/description/index.html b/spp_farmer_registry_demo/static/description/index.html
index 992dd785..b86e8660 100644
--- a/spp_farmer_registry_demo/static/description/index.html
+++ b/spp_farmer_registry_demo/static/description/index.html
@@ -367,33 +367,35 @@
OpenSPP Farmer Registry Demo
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:917d0d1a9f8139fbfedba0443e3dae58690ff9c18ed74c1f8b23e0f68de6dfcd
+!! source digest: sha256:force_regen
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Demo data generator for the OpenSPP Farmer Registry. Creates 8 named
farmer personas with complete Philippine farm profiles, 5 agricultural
subsidy programs with CEL eligibility expressions, and optionally
generates ~730 volume farms from deterministic blueprints using a seeded
-random generator (seed=42) for reproducible output.
+random generator (seed=42) for reproducible output.
Key Capabilities
-- Generate 8 fixed story farms with complete profiles (Maria Santos,
-Juan Dela Cruz, Rosa Garcia, Amir Mangudadatu, Sofia Martinez, Ramon
-dela Cruz, Sittie Pangandaman, Danilo Villanueva)
-- Create 5 demo programs (Input Subsidy, Equipment Grant, Livestock
-Support, Climate Resilience, Aquaculture Support) with CEL-based
-eligibility
-- Generate ~730 deterministic volume farms from 21 blueprints via seeded
-random generator (seed=42)
-- Create 2 farm cooperatives (Nueva Ecija Rice Cooperative, BARMM
-Farmers Federation) demonstrating group-of-groups hierarchy
-- Include 3 edge case personas for testing eligibility boundaries (large
-commercial, idle land, new farmer)
-- Install Logic Packs with CEL expressions for eligibility and benefit
-calculations
-- Distribute farms geographically across 8 Philippine provinces with GPS
-coordinates
+- 8 Fixed Story Farms with hardcoded profiles (Maria Santos, Juan
+Dela Cruz, Rosa Garcia, Amir Mangudadatu, Sofia Martinez, Ramon dela
+Cruz, Sittie Pangandaman, Danilo Villanueva)
+- ~730 Volume Farms generated deterministically from 21 blueprints
+via SeededFarmGenerator with random.Random(seed=42) — same
+seed always produces identical farms, members, and activities
+- 5 Demo Programs with CEL-based eligibility and benefit formulas
+(Input Subsidy, Equipment Grant, Livestock Support, Climate
+Resilience, Aquaculture Support)
+- 2 Farm Cooperatives demonstrating group-of-groups hierarchy (Nueva
+Ecija Rice Cooperative, BARMM Farmers Federation)
+- 3 Edge Case Personas for testing eligibility boundaries (AgriCorp
+Holdings, Idle Land Farm, New Farmer)
+- Install Logic Packs from spp_studio for eligibility rules
+- Create program cycles with entitlements and payments
+- Create change requests at various workflow stages
+- Geographic distribution across 8 Philippine provinces with GPS
+coordinates and land parcel polygons
@@ -410,13 +412,9 @@
Key Models
| spp.farmer.demo.generator |
-Core demo generator with all
+ | Core demo generator wizard with all
generation logic |
-| spp.farmer.demo.wizard |
-Wizard interface (inherits from
-generator) |
-
@@ -424,12 +422,56 @@ Key Models
Configuration
After installing:
-- Navigate to Settings > Demo Data > Load Farmer Registry Demo
-- The wizard opens with options for demo mode (Sales, Training,
-Testing, Complete)
+- Navigate to Settings > Demo Data > Load Farmer Demo
- Click “Load Demo Data” to generate
+
+
Demo Programs
+
All programs use CEL expressions with activated registry variables:
+
+- Input Subsidy Program: Per-hectare scaling for smallholders with
+productive land
+- Equipment Grant Program: Experience-based grant for farmers with
+2+ years
+- Livestock Support Program: Per-head scaling for livestock owners
+- Climate Resilience Program: Targets farms with idle land
+- Aquaculture Support Program: Activity-specific support for
+aquaculture farmers
+
+
+
+
Seeded Volume Generation
+
The SeededFarmGenerator uses random.Random(seed=42) for all
+structural choices:
+
+- Farm names from Filipino last name pools
+- Member names (given + family) from locale-specific pools
+- Farm sizes, experience years, GPS coordinates
+- Activity quantities (crop areas, livestock counts, aquaculture)
+
+
Running the generator twice with the same seed produces identical
+output.
+
+
+
Security
+
+
+
+
+
+
+| Group |
+Access |
+
+
+
+| spp_security.group_spp_admin |
+Full CRUD |
+
+
+
+
Dependencies
spp_starter_farmer_registry, spp_demo,
diff --git a/spp_grm_demo/README.rst b/spp_grm_demo/README.rst
index fcccf247..cbf53f0c 100644
--- a/spp_grm_demo/README.rst
+++ b/spp_grm_demo/README.rst
@@ -7,7 +7,7 @@ OpenSPP GRM Demo Data
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
+ !! source digest: sha256:force_regen
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
@@ -24,9 +24,12 @@ OpenSPP GRM Demo Data
Demo data generator for the Grievance Redress Mechanism. Creates both
story-based tickets linked to specific personas (Juan Dela Cruz, Ibrahim
-Hassan, Fatima Al-Rahman) and volume tickets using scenario templates.
+Hassan, Fatima Al-Rahman, Ahmed Said, David Martinez, Maria Santos, Rosa
+Garcia, Carlos Morales) and volume tickets using scenario templates.
Simulates realistic ticket workflows including resolution paths,
-escalations, and timeline distribution.
+escalations, and timeline distribution. Uses Faker for locale-aware
+random data (non-deterministic — each run produces different volume
+tickets).
Key Capabilities
~~~~~~~~~~~~~~~~
@@ -109,7 +112,8 @@ workflow demonstrations.
Dependencies
~~~~~~~~~~~~
-``spp_demo``, ``spp_grm``, ``spp_security``
+``spp_demo``, ``spp_grm``, ``spp_grm_registry``, ``spp_grm_programs``,
+``spp_security``, ``faker`` (Python)
**Table of contents**
diff --git a/spp_grm_demo/readme/DESCRIPTION.md b/spp_grm_demo/readme/DESCRIPTION.md
index 5e108752..017c859f 100644
--- a/spp_grm_demo/readme/DESCRIPTION.md
+++ b/spp_grm_demo/readme/DESCRIPTION.md
@@ -1,4 +1,4 @@
-Demo data generator for the Grievance Redress Mechanism. Creates both story-based tickets linked to specific personas (Juan Dela Cruz, Ibrahim Hassan, Fatima Al-Rahman) and volume tickets using scenario templates. Simulates realistic ticket workflows including resolution paths, escalations, and timeline distribution.
+Demo data generator for the Grievance Redress Mechanism. Creates both story-based tickets linked to specific personas (Juan Dela Cruz, Ibrahim Hassan, Fatima Al-Rahman, Ahmed Said, David Martinez, Maria Santos, Rosa Garcia, Carlos Morales) and volume tickets using scenario templates. Simulates realistic ticket workflows including resolution paths, escalations, and timeline distribution. Uses Faker for locale-aware random data (non-deterministic — each run produces different volume tickets).
### Key Capabilities
@@ -50,4 +50,4 @@ Story personas align with `spp_mis_demo_v2` and `spp_case_demo` for cross-module
### Dependencies
-`spp_demo`, `spp_grm`, `spp_security`
+`spp_demo`, `spp_grm`, `spp_grm_registry`, `spp_grm_programs`, `spp_security`, `faker` (Python)
diff --git a/spp_grm_demo/static/description/index.html b/spp_grm_demo/static/description/index.html
index 390c5763..d35f158b 100644
--- a/spp_grm_demo/static/description/index.html
+++ b/spp_grm_demo/static/description/index.html
@@ -367,14 +367,17 @@
OpenSPP GRM Demo Data
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
+!! source digest: sha256:force_regen
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Demo data generator for the Grievance Redress Mechanism. Creates both
story-based tickets linked to specific personas (Juan Dela Cruz, Ibrahim
-Hassan, Fatima Al-Rahman) and volume tickets using scenario templates.
+Hassan, Fatima Al-Rahman, Ahmed Said, David Martinez, Maria Santos, Rosa
+Garcia, Carlos Morales) and volume tickets using scenario templates.
Simulates realistic ticket workflows including resolution paths,
-escalations, and timeline distribution.
+escalations, and timeline distribution. Uses Faker for locale-aware
+random data (non-deterministic — each run produces different volume
+tickets).
Key Capabilities
@@ -477,7 +480,8 @@ Integration
Dependencies
-
spp_demo, spp_grm, spp_security
+
spp_demo, spp_grm, spp_grm_registry, spp_grm_programs,
+spp_security, faker (Python)
Table of contents
diff --git a/spp_mis_demo_v2/README.md b/spp_mis_demo_v2/README.md
deleted file mode 100644
index 2076eaad..00000000
--- a/spp_mis_demo_v2/README.md
+++ /dev/null
@@ -1,346 +0,0 @@
-# OpenSPP MIS Demo V2
-
-## Overview
-
-This module provides Demo Generator V2 for SP-MIS programs, following the simplified
-"Fixed Stories + Volume" architecture. It creates predictable demo data that integrates
-with the demo stories from `spp_demo` and showcases CEL expressions with Logic Packs
-from `spp_studio`.
-
-## Features
-
-- **Demo Programs**: 6 programs with CEL eligibility expressions
-- **Logic Pack Integration**: Programs link to reusable Logic Packs
-- **CEL Expression Showcase**: Demonstrates member queries, metrics, and constants
-- **Story Enrollments**: 9 demo personas with memorable names
-- **Change Requests**: All 11 CR types with approval workflows
-- **Multi-Mode Wizard**: Sales, Training, Testing, and Complete modes
-
-## Demo Programs
-
-The 6 demo programs showcase different CEL expression patterns using **activated
-registry variables**:
-
-| Program | Target | CEL Pattern | Logic Pack |
-| ------------------------ | ---------- | --------------------------------------------- | ------------------------ |
-| Universal Child Grant | Group | `child_count > 0` (variable) | child_benefit |
-| Elderly Social Pension | Individual | `age >= retirement_age` (computed + constant) | social_pension |
-| Emergency Relief Fund | Group | `dependency_ratio >= 1.5` (computed variable) | vulnerability_assessment |
-| Cash Transfer Program | Group | `hh_total_income < poverty_line` (aggregate) | cash_transfer_basic |
-| Disability Support Grant | Group | `has_disabled_member` (computed variable) | disability_assistance |
-| Food Assistance | Individual | `r.active == true` (simple field) | None (inline CEL) |
-
-## Demo Stories (12 Personas)
-
-### Primary Stories - Eligible
-
-| Persona | MIS Program | GRM Ticket | Case Story |
-| ---------------- | ------------------------ | ------------------------- | ------------------------ |
-| Maria Santos | Cash Transfer Program | Graduation inquiry | Santos Family Support |
-| Juan Dela Cruz | Cash Transfer Program | Payment not received | Dela Cruz Emergency |
-| Rosa Garcia | Elderly Pension + Food | Delivery schedule inquiry | Garcia Elder Care |
-| Carlos Morales | Universal Child Grant | Adding new child | Morales Household Crisis |
-| Ibrahim Hassan | Emergency Relief Fund | Resettlement support | Hassan Resettlement |
-| David Martinez | Disability Support Grant | Grant application status | Martinez Disability |
-| Fatima Al-Rahman | Universal Child Grant | Eligibility inquiry | Al-Rahman Assessment |
-| Ahmed Said | Cash Transfer Program | Multiple tickets (3) | Said Family Support |
-
-### Rejection Demonstrations - Ineligible
-
-| Persona | Program Applied For | Rejection Reason |
-| --------------------- | ---------------------- | ------------------------- |
-| Mary Johnson | Elderly Social Pension | Below retirement age (55) |
-| Childless Household | Universal Child Grant | No children under 18 |
-| High Income Household | Cash Transfer Program | Income above poverty line |
-
-## CEL Expression Examples
-
-These expressions use **activated registry variables** for cleaner, more maintainable
-eligibility rules.
-
-### Universal Child Grant
-
-```cel
-r.is_group == true and child_count > 0
-```
-
-Uses the `child_count` aggregate variable (automatically counts members under 18).
-
-### Elderly Social Pension
-
-```cel
-r.is_group == false and age >= retirement_age
-```
-
-Uses the `age` computed variable and `retirement_age` constant (default: 60).
-
-### Emergency Relief Fund
-
-```cel
-r.is_group == true and (dependency_ratio >= 1.5 or (is_female_headed and elderly_count > 0))
-```
-
-Uses `dependency_ratio`, `is_female_headed`, and `elderly_count` variables for
-vulnerability targeting.
-
-### Cash Transfer Program
-
-```cel
-r.is_group == true and hh_total_income < poverty_line and hh_size >= 2
-```
-
-Uses `hh_total_income` aggregate, `poverty_line` constant, and `hh_size` aggregate.
-
-### Disability Support Grant
-
-```cel
-r.is_group == true and has_disabled_member
-```
-
-Uses the `has_disabled_member` computed variable (checks `is_person_with_disability` on
-members).
-
-## Cross-Module Integration
-
-### Integrated Demo Ecosystem
-
-MIS Demo V2 is designed to work seamlessly with GRM and Case Management demos:
-
-```
-┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
-│ spp_mis_demo_v2│ │ spp_grm_demo │ │ spp_case_demo │
-│ │ │ │ │ │
-│ • Programs │────▶│ • Tickets │────▶│ • Cases │
-│ • Enrollments │ │ • Escalations │ │ • Interventions│
-│ • Payments │ │ • Resolutions │ │ • Plans │
-└─────────────────┘ └─────────────────┘ └─────────────────┘
- │ │ │
- └───────────────────────┴───────────────────────┘
- Shared Personas (8 beneficiaries)
-```
-
-### Automatic Cross-Module Generation
-
-The MIS Demo wizard can automatically generate GRM tickets and Cases when those modules
-are installed. Simply enable the options in the wizard:
-
-- **Generate GRM Demo**: Creates story-based tickets + volume tickets (requires
- `spp_grm_demo`)
-- **Generate Case Demo**: Creates story-based cases + volume cases (requires
- `spp_case_demo`)
-
-This eliminates the need to run separate wizards for each module.
-
-### Manual Demo Order (Alternative)
-
-If you prefer to run demos separately:
-
-1. **spp_demo** - Creates base registrants with persona names
-2. **spp_mis_demo_v2** - Creates programs and enrolls personas
-3. **spp_grm_demo** - Creates tickets referencing program issues
-4. **spp_case_demo** - Creates cases escalated from GRM
-
-### Demo Scenario: Juan Dela Cruz Journey
-
-1. **MIS**: Enrolled in Cash Transfer Program, receiving $150/month
-2. **GRM**: Files ticket "Payment not received after house fire"
-3. **GRM**: Ticket escalated due to emergency situation
-4. **Case**: Emergency case opened with shelter and cash assistance interventions
-5. **Case**: Family stabilized, case moves to monitoring phase
-
-### Demo Scenario: David Martinez Journey
-
-1. **MIS**: Applies for Disability Support Grant for son Miguel
-2. **GRM**: Files ticket asking about application status
-3. **GRM**: Referred to case management for comprehensive support
-4. **Case**: Case opened for equipment assistance and education enrollment
-5. **MIS**: Grant approved - $175/month (base $100 + $75 per disabled member)
-
-## Dependencies
-
-- `spp_demo` - Demo story infrastructure
-- `spp_programs` - Program management
-- `spp_registry` - Registry module
-- `spp_cel_domain` - CEL variable system (ADR-008, ADR-017)
-- `spp_studio` - Logic Packs and expressions
-- `spp_change_request_v2` - Change request workflows
-- Optional: `spp_grm_demo` - For cross-module GRM integration
-- Optional: `spp_case_demo` - For cross-module case integration
-
-## Installation
-
-1. Install the module through Odoo Apps menu
-2. Ensure `spp_demo` is installed with stories generated
-
-## Usage
-
-### Using the Wizard
-
-1. Navigate to **Programs > Generate MIS Demo Data**
-2. Select **Demo Mode**:
- - **Sales Demo**: Fixed stories, minimal data, fast
- - **Partner Training**: Full programs, Logic Packs, comprehensive
- - **Developer Testing**: Volume data, random generation, scale testing
- - **Complete Demo**: All features enabled
-3. Configure additional options as needed
-4. Click **Generate Demo Data**
-
-### Demo Mode Presets
-
-| Mode | Stories | Programs | Logic Packs | Volume | Personas |
-| -------- | ------- | -------- | ----------- | ------- | -------- |
-| Sales | Yes | Yes | No | No | No |
-| Training | Yes | Yes | Yes | Minimal | Yes |
-| Testing | Yes | Yes | No | High | No |
-| Complete | Yes | Yes | Yes | Yes | Yes |
-
-### Programmatic Usage
-
-```python
-# Create and run the generator with training mode
-generator = env['spp.mis.demo.wizard'].create({
- 'name': 'Training Demo',
- 'demo_mode': 'training',
- 'install_logic_packs': True,
- 'include_test_personas': True,
- 'create_demo_programs': True,
- 'enroll_demo_stories': True,
-})
-generator.action_generate_demo_data()
-```
-
-## Change Request Integration
-
-The demo creates change requests covering all 11 CR types:
-
-### CR Types Demonstrated
-
-| Type | Description | Demo State |
-| ----------------- | ------------------------- | ------------------ |
-| edit_individual | Basic data update | Approved |
-| update_id | ID document update | Approved |
-| exit_registrant | Registry exit | Approved + Applied |
-| add_member | Add household member | Approved |
-| remove_member | Remove member | Pending |
-| transfer_member | Inter-household transfer | Pending |
-| change_hoh | Head of household change | Approved |
-| create_group | Create new group | Draft |
-| split_household | Split into two households | Draft |
-| merge_registrants | Merge duplicates | Rejected |
-
-### Workflow States Demonstrated
-
-- **Draft**: New CRs not yet submitted
-- **Pending**: Awaiting approval
-- **Approved**: Approved (may or may not be applied)
-- **Rejected**: Rejected with reason documented
-- **Revision**: Sent back for correction
-
-## Logic Pack Integration
-
-Programs link to pre-built Logic Packs from `spp_studio`:
-
-```python
-DEMO_LOGIC_PACKS = [
- "child_benefit", # Universal Child Grant
- "social_pension", # Elderly Social Pension
- "vulnerability_assessment", # Emergency Relief
- "cash_transfer_basic", # Cash Transfer Program
- "disability_assistance", # Disability Support Grant
-]
-```
-
-### Installing Logic Packs
-
-```python
-from odoo.addons.spp_mis_demo_v2.models.demo_variables import install_demo_packs
-installed = install_demo_packs(env)
-```
-
-## Registry Variables
-
-On module installation, registry variables are **automatically activated** and ready for
-use in Logic Studio and program expressions. This includes both standard variables from
-`spp_studio` and demo-specific variables.
-
-### Standard Variables (from spp_studio)
-
-| Category | Variables |
-| ----------------------------- | --------------------------------------------------------------------------------------------------------- |
-| **Demographics** | `age` |
-| **Household Composition** | `hh_size`, `child_count`, `elderly_count`, `working_age_count` |
-| **Household Characteristics** | `is_female_headed`, `is_elderly_headed`, `has_disabled_member`, `has_pregnant_member`, `dependency_ratio` |
-| **Economic** | `per_capita_income`, `hh_total_income`, `hh_avg_income` |
-| **Constants** | `poverty_line`, `retirement_age`, `child_age_limit`, `per_child_benefit`, `base_benefit` |
-
-### Demo-Specific Variables
-
-| Variable | Type | Default | Description |
-| ----------------------------- | --------- | ------- | --------------------------- |
-| `vulnerability_threshold` | constant | 70 | Emergency eligibility score |
-| `base_child_grant` | constant | 50 | Per-child benefit amount |
-| `disability_grant_base` | constant | 100 | Base disability amount |
-| `disability_grant_per_member` | constant | 75 | Per disabled member bonus |
-| `emergency_tier_1` | constant | 500 | Tier 1 emergency amount |
-| `emergency_tier_2` | constant | 400 | Tier 2 emergency amount |
-| `emergency_tier_3` | constant | 300 | Tier 3 emergency amount |
-| `elderly_pension_amount` | constant | 100 | Fixed pension amount |
-| `cash_transfer_amount` | constant | 150 | Fixed transfer amount |
-| `disabled_count` | aggregate | - | Count of disabled members |
-
-### Variable Activation
-
-Variables are activated during module installation via `post_init_hook`. The activation:
-
-1. Finds variables by XML ID (e.g., `spp_studio.var_age`)
-2. Activates any in `draft` state
-3. Skips already active variables
-4. Logs results for troubleshooting
-
-```
-[spp.mis.demo] Standard variables: 18 activated, 0 already active, 0 errors
-[spp.mis.demo] Demo variables: 10 activated, 0 already active, 0 errors
-[spp.mis.demo] Registry variables ready: 28 activated, 0 skipped, 0 errors
-```
-
-## Technical Details
-
-### Models
-
-- `spp.mis.demo.generator` - Core generator with all logic
-- `spp.mis.demo.wizard` - Wizard interface (inherits generator)
-
-### Key Methods
-
-- `action_generate()` - Main entry point
-- `_create_demo_programs()` - Creates 6 programs with CEL
-- `_enroll_demo_stories()` - Enrolls personas
-- `_install_logic_packs()` - Installs required Logic Packs
-- `_create_test_personas()` - Creates test personas for Studio
-- `_create_change_requests()` - Creates CR demos
-
-### Data Files
-
-- `data/demo_constants.xml` - CEL variable definitions
-- `data/demo_personas.xml` - Test personas for Logic Studio
-- `data/demo_programs.xml` - Program configurations
-
-## V3 Architecture Alignment
-
-This module follows the V3 Architecture principles:
-
-- **CEL Integration** - All eligibility uses CEL expressions
-- **Logic Packs** - Reusable expression bundles
-- **Fixed Stories + Volume** - Predictable personas plus random data
-- **Multi-Mode** - Different presets for different use cases
-- **Change Requests** - Full CR workflow coverage
-
-## License
-
-LGPL-3
-
-## Credits
-
-**Authors**: OpenSPP.org
-
-**Maintainers**: jeremi, gonzalesedwin1123
diff --git a/spp_mis_demo_v2/README.rst b/spp_mis_demo_v2/README.rst
index d57f29d6..912201b5 100644
--- a/spp_mis_demo_v2/README.rst
+++ b/spp_mis_demo_v2/README.rst
@@ -7,7 +7,7 @@ OpenSPP MIS Demo V2
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:0c87dff4fb6bacbc90c381609a5bab672c40a2c8b950ad5e3a54eca0ac5ba0d5
+ !! source digest: sha256:force_regen
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
@@ -22,31 +22,39 @@ OpenSPP MIS Demo V2
|badge1| |badge2| |badge3|
-Demo data generator for SP-MIS programs. Creates 6 social protection
+Demo data generator for SP-MIS programs. Creates 7 social protection
programs with CEL eligibility expressions, enrolls 8 demo personas with
-payment histories, and optionally generates volume data for testing.
-Activates registry variables from ``spp_studio`` and installs Logic
-Packs for eligibility rules.
+payment histories, and optionally generates ~730 deterministic
+households from seeded blueprints (``seed=42``) for reproducible volume
+data. Activates registry variables from ``spp_studio`` and installs
+Logic Packs for eligibility rules.
Key Capabilities
~~~~~~~~~~~~~~~~
-- Generate 6 programs (Child Grant, Elderly Pension, Emergency Relief,
- Cash Transfer, Disability Support, Food Assistance) with CEL
- expressions
+- Generate 7 programs (Universal Child Grant, Conditional Child Grant,
+ Elderly Pension, Emergency Relief, Cash Transfer, Disability Support,
+ Food Assistance) with CEL expressions
- Enroll 8 demo personas with predefined stories and payment histories
covering all demo scenarios
+- Generate ~730 deterministic households with ~2555 members from 28
+ blueprints via ``SeededVolumeGenerator`` with
+ ``random.Random(seed=42)`` — same seed always produces identical
+ output
- Install Logic Packs from ``spp_studio`` for eligibility rules
(child_benefit, social_pension, vulnerability_assessment,
cash_transfer_basic, disability_assistance)
- Activate registry variables (age, child_count, hh_total_income,
dependency_ratio, etc.) via post_init_hook
-- Generate volume data with configurable random enrollments for
- dashboard testing
+- 4 demo modes (Sales, Training, Testing, Complete) with automatic field
+ defaults
+- Multi-locale support for name generation (fil_PH, si_LK, fr_TG)
+- Geographic data loading for Philippines, Sri Lanka, and Togo
- Create change requests at various workflow stages (draft, pending,
approved, rejected)
-- Cross-module integration: automatically creates GRM tickets and case
- records when those modules are installed
+- Cross-module integration: automatically creates GRM tickets, case
+ records, and Claim 169 QR credentials when those modules are installed
+- Fairness analysis demo data and PRISM API client creation
Key Models
~~~~~~~~~~
@@ -68,18 +76,12 @@ After installing:
Testing, Complete)
3. Click "Load Demo Data" to generate
-For automatic generation on install:
-
-.. code:: bash
-
- ODOO_INIT_MODULES=spp_mis_demo_v2 docker compose --profile ui up -d
-
For programmatic generation:
.. code:: python
- generator = env['spp.mis.demo.wizard'].create({'name': 'Demo'})
- generator.action_generate_demo_data()
+ wizard = env['spp.mis.demo.wizard'].create({})
+ wizard.action_generate_demo_data()
Demo Programs
~~~~~~~~~~~~~
@@ -88,6 +90,8 @@ All programs use CEL expressions with activated registry variables:
- **Universal Child Grant**: ``r.is_group == true and child_count > 0``
(member aggregation)
+- **Conditional Child Grant**: First 1,000 days targeting for young
+ children
- **Elderly Social Pension**:
``r.is_group == false and age >= retirement_age`` (age computation)
- **Emergency Relief Fund**:
@@ -102,6 +106,23 @@ All programs use CEL expressions with activated registry variables:
- **Food Assistance**: ``r.is_registrant == true and r.active == true``
(simple field comparison)
+Seeded Volume Generation
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``SeededVolumeGenerator`` uses ``random.Random(seed=42)`` for all
+structural choices:
+
+- Ages, incomes, genders, and names from locale-specific pools
+- Birthdates, registration dates, GPS coordinates
+- Household structure from 28 deterministic blueprints across 5
+ categories (young families, middle-age, elderly, working-age,
+ extended/vulnerable)
+- Membership realism applied post-generation (83% enrolled, 10% exited,
+ 5% paused, 2% not eligible)
+
+Running the generator twice with the same seed produces identical
+output.
+
UI Location
~~~~~~~~~~~
diff --git a/spp_mis_demo_v2/__manifest__.py b/spp_mis_demo_v2/__manifest__.py
index 82e6148c..b166e37e 100644
--- a/spp_mis_demo_v2/__manifest__.py
+++ b/spp_mis_demo_v2/__manifest__.py
@@ -30,6 +30,8 @@
"spp_api_v2_gis",
# QR Credentials (Claim 169)
"spp_claim_169",
+ # Banking (for bank account demo data)
+ "spp_banking",
# Demo-specific extensions
],
"external_dependencies": {"python": ["requests"]},
diff --git a/spp_mis_demo_v2/data/demo_personas.xml b/spp_mis_demo_v2/data/demo_personas.xml
index beeabe03..208fdaa0 100644
--- a/spp_mis_demo_v2/data/demo_personas.xml
+++ b/spp_mis_demo_v2/data/demo_personas.xml
@@ -27,13 +27,13 @@
Maria Santos
SUCCESS STORY: 42-year-old rice farmer and mother of two from rural Laguna province. Started with 2.5 hectares and struggled with seasonal income. After 5 months in the Cash Transfer Program, she saved enough to buy better seeds and tools. Recently graduated from the program - demonstrating the pathway from poverty to self-sufficiency. Now mentors other farmers in her community.
+ >SUCCESS STORY: 42-year-old mother of two from rural Laguna province. Struggled with seasonal income and lived below the poverty line. After 5 months in the Cash Transfer Program, she saved enough to improve her household livelihood. Recently graduated from the program - demonstrating the pathway from poverty to self-sufficiency. Now mentors other beneficiaries in her community.
eligible
10
{"name": "Maria Santos", "age": 42, "is_registrant": true, "is_group": false, "active": true, "farm_size_hectares": 2.5, "income": 8000, "occupation": "rice_farmer", "region": "rural"}
+ >{"name": "Maria Santos", "age": 42, "is_registrant": true, "is_group": false, "active": true, "income": 8000, "region": "rural"}
@@ -64,18 +64,18 @@
>{"name": "Carlos Morales Household", "is_registrant": true, "is_group": true, "active": true, "hh_size": 5, "child_count": 3, "income": 4000, "head_occupation": "tricycle_driver", "spouse_occupation": "laundry_worker"}
-
+
- Ibrahim Hassan
+ Ramon Gutierrez
EMERGENCY RESPONSE: 50-year-old farmer displaced by recent flooding in his coastal village. Lost his home, crops, and livelihood. Currently staying in an evacuation center with vulnerability score of 85 (high). Receives Tier 2 emergency payments ($400) based on tiered vulnerability assessment - demonstrating crisis response targeting and ternary CEL expressions for benefit calculation.
+ >EMERGENCY RESPONSE: 50-year-old person displaced by recent flooding in his coastal village. Lost his home and livelihood. Currently staying in an evacuation center with vulnerability score of 85 (high). Receives Tier 2 emergency payments ($400) based on tiered vulnerability assessment - demonstrating crisis response targeting and ternary CEL expressions for benefit calculation.
eligible
40
{"name": "Ibrahim Hassan", "age": 50, "is_registrant": true, "is_group": false, "active": true, "displacement_status": "displaced", "vulnerability_score": 85, "shelter_type": "evacuation_center", "previous_occupation": "farmer"}
+ >{"name": "Ramon Gutierrez", "age": 50, "is_registrant": true, "is_group": false, "active": true, "displacement_status": "displaced", "vulnerability_score": 85, "shelter_type": "evacuation_center"}
@@ -106,9 +106,9 @@
>{"name": "Juan Dela Cruz", "age": 45, "is_registrant": true, "is_group": true, "active": true, "hh_size": 4, "income": 3500, "displacement_status": "displaced", "displacement_reason": "house_fire", "has_grm_ticket": true, "has_case": true}
-
+
- Ahmed Said
+ Roberto Castillo
MULTI-TICKET GRM: 38-year-old construction worker and father of 4 enrolled in Cash Transfer Program. Has filed multiple GRM tickets over time - payment delay (resolved), bank account update (resolved), and general inquiry about next payment. Demonstrates how the system tracks beneficiary interaction history and ticket patterns.
@@ -117,12 +117,12 @@
58
{"name": "Ahmed Said", "age": 38, "is_registrant": true, "is_group": false, "active": true, "income": 5000, "occupation": "construction_worker", "child_count": 4, "grm_ticket_count": 3}
+ >{"name": "Roberto Castillo", "age": 38, "is_registrant": true, "is_group": false, "active": true, "income": 5000, "occupation": "construction_worker", "child_count": 4, "grm_ticket_count": 3}
-
+
- Fatima Al-Rahman
+ Teresa Villanueva
INFORMATION REQUEST: 32-year-old mother of 2 who filed a GRM ticket asking about program eligibility requirements. Her ticket was quickly resolved with information provided. Demonstrates the system's ability to handle inquiries efficiently. She is now being assessed for Universal Child Grant enrollment.
@@ -131,16 +131,16 @@
60
{"name": "Fatima Al-Rahman", "age": 32, "is_registrant": true, "is_group": false, "active": true, "income": 4500, "child_count": 2, "marital_status": "married", "assessment_status": "pending"}
+ >{"name": "Teresa Villanueva", "age": 32, "is_registrant": true, "is_group": false, "active": true, "income": 4500, "child_count": 2, "marital_status": "married", "assessment_status": "pending"}
-
+
- Mary Johnson
+ Lorna Pascual
REJECTION CASE - AGE: 55-year-old woman who applied for Elderly Social Pension. Although she has no formal pension and low income, she was REJECTED because she doesn't meet the age requirement (retirement_age = 65). Her application demonstrates how the system properly enforces age-based eligibility rules. She should reapply in 10 years when she turns 65.
@@ -149,7 +149,7 @@
100
{"name": "Mary Johnson", "age": 55, "is_registrant": true, "is_group": false, "active": true, "income": 3000, "has_formal_pension": false, "rejection_reason": "below_retirement_age"}
+ >{"name": "Lorna Pascual", "age": 55, "is_registrant": true, "is_group": false, "active": true, "income": 3000, "has_formal_pension": false, "rejection_reason": "below_retirement_age"}
diff --git a/spp_mis_demo_v2/data/event_types.xml b/spp_mis_demo_v2/data/event_types.xml
index 9280ef5b..e0d7b422 100644
--- a/spp_mis_demo_v2/data/event_types.xml
+++ b/spp_mis_demo_v2/data/event_types.xml
@@ -39,7 +39,7 @@
training
Training sessions for beneficiaries including agricultural practices, financial literacy, and program orientation.
+ >Training sessions for beneficiaries including financial literacy, life skills, and program orientation.
visit
both
internal
@@ -52,7 +52,7 @@
extension_visit
Field extension visits by agricultural officers to provide technical assistance and monitor progress.
+ >Field extension visits by social welfare officers to provide technical assistance and monitor progress.
visit
both
internal
@@ -222,18 +222,18 @@
-->
+ Based on: completeness of data, household conditions, recommendations made -->
10
data_completeness,farm_condition_score,recommendations_count
+ >data_completeness,household_condition_score,recommendations_count
visit_quality_score
expression
(int(data_completeness) * 0.4) + (int(farm_condition_score) * 0.4) + (int(recommendations_count) * 2.0)
+ >(int(data_completeness) * 0.4) + (int(household_condition_score) * 0.4) + (int(recommendations_count) * 2.0)

-Demo data generator for SP-MIS programs. Creates 6 social protection
+
Demo data generator for SP-MIS programs. Creates 7 social protection
programs with CEL eligibility expressions, enrolls 8 demo personas with
-payment histories, and optionally generates volume data for testing.
-Activates registry variables from spp_studio and installs Logic
-Packs for eligibility rules.
+payment histories, and optionally generates ~730 deterministic
+households from seeded blueprints (seed=42) for reproducible volume
+data. Activates registry variables from spp_studio and installs
+Logic Packs for eligibility rules.
Key Capabilities
-- Generate 6 programs (Child Grant, Elderly Pension, Emergency Relief,
-Cash Transfer, Disability Support, Food Assistance) with CEL
-expressions
+- Generate 7 programs (Universal Child Grant, Conditional Child Grant,
+Elderly Pension, Emergency Relief, Cash Transfer, Disability Support,
+Food Assistance) with CEL expressions
- Enroll 8 demo personas with predefined stories and payment histories
covering all demo scenarios
+- Generate ~730 deterministic households with ~2555 members from 28
+blueprints via SeededVolumeGenerator with
+random.Random(seed=42) — same seed always produces identical
+output
- Install Logic Packs from spp_studio for eligibility rules
(child_benefit, social_pension, vulnerability_assessment,
cash_transfer_basic, disability_assistance)
- Activate registry variables (age, child_count, hh_total_income,
dependency_ratio, etc.) via post_init_hook
-- Generate volume data with configurable random enrollments for
-dashboard testing
+- 4 demo modes (Sales, Training, Testing, Complete) with automatic field
+defaults
+- Multi-locale support for name generation (fil_PH, si_LK, fr_TG)
+- Geographic data loading for Philippines, Sri Lanka, and Togo
- Create change requests at various workflow stages (draft, pending,
approved, rejected)
-- Cross-module integration: automatically creates GRM tickets and case
-records when those modules are installed
+- Cross-module integration: automatically creates GRM tickets, case
+records, and Claim 169 QR credentials when those modules are installed
+- Fairness analysis demo data and PRISM API client creation
@@ -427,14 +435,10 @@
Configuration
Testing, Complete)
- Click “Load Demo Data” to generate
-
For automatic generation on install:
-
-ODOO_INIT_MODULES=spp_mis_demo_v2 docker compose --profile ui up -d
-
For programmatic generation:
-generator = env['spp.mis.demo.wizard'].create({'name': 'Demo'})
-generator.action_generate_demo_data()
+wizard = env['spp.mis.demo.wizard'].create({})
+wizard.action_generate_demo_data()
@@ -443,6 +447,8 @@
Demo Programs
- Universal Child Grant: r.is_group == true and child_count > 0
(member aggregation)
+- Conditional Child Grant: First 1,000 days targeting for young
+children
- Elderly Social Pension:
r.is_group == false and age >= retirement_age (age computation)
- Emergency Relief Fund:
@@ -458,6 +464,22 @@
Demo Programs
(simple field comparison)
+
+
Seeded Volume Generation
+
The SeededVolumeGenerator uses random.Random(seed=42) for all
+structural choices:
+
+- Ages, incomes, genders, and names from locale-specific pools
+- Birthdates, registration dates, GPS coordinates
+- Household structure from 28 deterministic blueprints across 5
+categories (young families, middle-age, elderly, working-age,
+extended/vulnerable)
+- Membership realism applied post-generation (83% enrolled, 10% exited,
+5% paused, 2% not eligible)
+
+
Running the generator twice with the same seed produces identical
+output.
+
UI Location
diff --git a/spp_mis_demo_v2/tests/test_blueprint_reproducibility.py b/spp_mis_demo_v2/tests/test_blueprint_reproducibility.py
index fdc3082e..0a92eca2 100644
--- a/spp_mis_demo_v2/tests/test_blueprint_reproducibility.py
+++ b/spp_mis_demo_v2/tests/test_blueprint_reproducibility.py
@@ -125,6 +125,7 @@ def test_eligibility_flags_exist_for_known_programs(self):
"""Each blueprint's eligibility flags reference valid program IDs."""
valid_program_ids = {
"universal_child_grant",
+ "conditional_child_grant",
"elderly_social_pension",
"emergency_relief_fund",
"cash_transfer_program",
diff --git a/spp_mis_demo_v2/tests/test_claim169_demo.py b/spp_mis_demo_v2/tests/test_claim169_demo.py
index 6259ba53..a6b57ea9 100644
--- a/spp_mis_demo_v2/tests/test_claim169_demo.py
+++ b/spp_mis_demo_v2/tests/test_claim169_demo.py
@@ -30,12 +30,12 @@ def setUpClass(cls):
# Create test country for locale
cls.test_country = cls.env.ref("base.us")
- # Create a demo story registrant
+ # Create a demo story registrant (group named by family name)
cls.demo_registrant = cls.env["res.partner"].create(
{
- "name": "Maria Santos",
+ "name": "Santos",
"is_registrant": True,
- "is_group": False,
+ "is_group": True,
}
)
diff --git a/spp_mis_demo_v2/tests/test_demo_programs.py b/spp_mis_demo_v2/tests/test_demo_programs.py
index 6880fe0c..eb1ac8b1 100644
--- a/spp_mis_demo_v2/tests/test_demo_programs.py
+++ b/spp_mis_demo_v2/tests/test_demo_programs.py
@@ -428,28 +428,34 @@ def test_story_program_alignment_complete(self):
Each story persona should be enrolled in programs that match their:
- Demographics (age, household composition)
- Circumstances (income, disability status, vulnerability)
- - Story narrative (farmer, elderly, displaced, etc.)
+ - Story narrative (elderly, displaced, disability, etc.)
"""
from odoo.addons.spp_mis_demo_v2.models import demo_programs
# Expected story-to-program mappings based on V3 spec
expected_mappings = {
- # Maria Santos - Farmer success story with graduation from Cash Transfer
- "maria_santos": ["Cash Transfer Program"],
+ # Maria Santos - Cash Transfer (graduated) + Universal Child Grant + Food Assistance (individual)
+ "maria_santos": ["Cash Transfer Program", "Universal Child Grant", "Food Assistance"],
# Juan Dela Cruz - Cash Transfer recipient with GRM issue
"juan_dela_cruz": ["Cash Transfer Program"],
# Rosa Garcia - Elderly + Food Assistance
"rosa_garcia": ["Elderly Social Pension", "Food Assistance"],
# Carlos/Elena Morales - Household with 3 children
"carlos_elena_morales": ["Universal Child Grant"],
- # Ibrahim Hassan - Displaced/Vulnerable
- "ibrahim_hassan": ["Emergency Relief Fund"],
- # Fatima Al-Rahman - Food Assistance recipient
+ # Ramon Gutierrez - Emergency Relief (household) + Food Assistance (individual)
+ "ibrahim_hassan": ["Emergency Relief Fund", "Food Assistance"],
+ # Teresa Villanueva - Food Assistance recipient
"fatima_al_rahman": ["Food Assistance"],
# David/Sofia Martinez - Household with disabled member
- "david_martinez": ["Disability Support Grant"],
- # Ahmed Said - Background Cash Transfer
+ "david_sofia_martinez": ["Disability Support Grant"],
+ # Roberto Castillo - Background Cash Transfer
"ahmed_said": ["Cash Transfer Program"],
+ # Manuel Pangilinan - Elderly Social Pension (individual)
+ "manuel_gloria_elderly": ["Elderly Social Pension"],
+ # Pedro Reyes - Food Assistance
+ "pedro_reyes": ["Food Assistance"],
+ # Ana Mendoza - Food Assistance
+ "ana_mendoza": ["Food Assistance"],
}
for story_id, expected_programs in expected_mappings.items():
@@ -544,14 +550,14 @@ def test_disability_story_eligibility(self):
"""
from odoo.addons.spp_mis_demo_v2.models import demo_programs
- programs = demo_programs.get_programs_for_story("david_martinez")
+ programs = demo_programs.get_programs_for_story("david_sofia_martinez")
program_names = [p["name"] for p in programs]
self.assertIn("Disability Support Grant", program_names)
def test_emergency_story_eligibility(self):
- """Test Ibrahim Hassan meets emergency eligibility criteria.
+ """Test Ramon Gutierrez meets emergency eligibility criteria.
- Ibrahim should be eligible because:
+ Ramon Gutierrez should be eligible because:
- dependency_ratio >= 1.5 (using computed variable)
- OR is_female_headed and elderly_count > 0
"""
@@ -561,10 +567,100 @@ def test_emergency_story_eligibility(self):
program_names = [p["name"] for p in programs]
self.assertIn("Emergency Relief Fund", program_names)
+ # ===================================================================
+ # Compliance criteria tests
+ # ===================================================================
+
+ def test_cash_transfer_has_compliance_expression(self):
+ """Cash Transfer Program must define a compliance CEL expression."""
+ from odoo.addons.spp_mis_demo_v2.models import demo_programs
+
+ for prog in demo_programs.get_all_demo_programs():
+ if prog["id"] == "cash_transfer_program":
+ self.assertIn("compliance_cel_expression", prog)
+ self.assertEqual(
+ prog["compliance_cel_expression"],
+ "per_capita_income < poverty_line",
+ )
+ return
+ self.fail("Cash Transfer Program not found")
+
+ def test_conditional_child_grant_has_compliance_expression(self):
+ """Conditional Child Grant must define a compliance CEL expression."""
+ from odoo.addons.spp_mis_demo_v2.models import demo_programs
+
+ for prog in demo_programs.get_all_demo_programs():
+ if prog["id"] == "conditional_child_grant":
+ self.assertIn("compliance_cel_expression", prog)
+ self.assertEqual(
+ prog["compliance_cel_expression"],
+ "per_capita_income < income_threshold",
+ )
+ return
+ self.fail("Conditional Child Grant not found")
+
+ def test_conditional_child_grant_has_program_constants(self):
+ """Conditional Child Grant must override income_threshold to 2000."""
+ from odoo.addons.spp_mis_demo_v2.models import demo_programs
+
+ for prog in demo_programs.get_all_demo_programs():
+ if prog["id"] == "conditional_child_grant":
+ self.assertIn("program_constants", prog)
+ self.assertEqual(prog["program_constants"]["income_threshold"], "2000")
+ return
+ self.fail("Conditional Child Grant not found")
+
+ def test_programs_without_compliance_have_no_expression(self):
+ """Programs without compliance should not define compliance_cel_expression."""
+ from odoo.addons.spp_mis_demo_v2.models import demo_programs
+
+ programs_with_compliance = {"cash_transfer_program", "conditional_child_grant"}
+ for prog in demo_programs.get_all_demo_programs():
+ if prog["id"] not in programs_with_compliance:
+ expr = prog.get("compliance_cel_expression")
+ self.assertFalse(
+ expr,
+ f"Program '{prog['name']}' should not have compliance_cel_expression",
+ )
+
+ def test_santos_has_non_compliant_cycle(self):
+ """Santos enrollment must define non_compliant_cycle for compliance failure."""
+ from odoo.addons.spp_mis_demo_v2.models import demo_programs
+
+ enrollments = demo_programs.get_story_enrollments("maria_santos")
+ cash_transfer = [e for e in enrollments if e["program"] == "Cash Transfer Program"]
+ self.assertEqual(len(cash_transfer), 1)
+ self.assertIn("non_compliant_cycle", cash_transfer[0])
+ self.assertIn("days_back", cash_transfer[0]["non_compliant_cycle"])
+
+ def test_santos_has_graduated_days_back(self):
+ """Santos Cash Transfer must have graduated_days_back (exited after compliance failure)."""
+ from odoo.addons.spp_mis_demo_v2.models import demo_programs
+
+ enrollments = demo_programs.get_story_enrollments("maria_santos")
+ cash_transfer = [e for e in enrollments if e["program"] == "Cash Transfer Program"]
+ self.assertEqual(len(cash_transfer), 1)
+ self.assertIn("graduated_days_back", cash_transfer[0])
+
+ def test_santos_enrolled_in_universal_child_grant(self):
+ """Santos must also be enrolled in Universal Child Grant (partial exit story)."""
+ from odoo.addons.spp_mis_demo_v2.models import demo_programs
+
+ enrollments = demo_programs.get_story_enrollments("maria_santos")
+ programs = [e["program"] for e in enrollments]
+ self.assertIn("Universal Child Grant", programs)
+
+ def test_only_two_programs_have_compliance(self):
+ """Exactly 2 programs should have compliance CEL expressions."""
+ from odoo.addons.spp_mis_demo_v2.models import demo_programs
+
+ with_compliance = [p for p in demo_programs.get_all_demo_programs() if p.get("compliance_cel_expression")]
+ self.assertEqual(len(with_compliance), 2)
+
def test_rejected_story_documented(self):
"""Test that rejected application story is properly documented.
- Mary Johnson should be rejected for age requirement not met.
+ Lorna Pascual should be rejected for age requirement not met.
"""
from odoo.addons.spp_mis_demo_v2.models import demo_programs
diff --git a/spp_mis_demo_v2/tests/test_mis_demo_generator.py b/spp_mis_demo_v2/tests/test_mis_demo_generator.py
index e51123d0..38d7dfe2 100644
--- a/spp_mis_demo_v2/tests/test_mis_demo_generator.py
+++ b/spp_mis_demo_v2/tests/test_mis_demo_generator.py
@@ -460,13 +460,17 @@ def setUpClass(cls):
# Based on demo_stories.py definitions
cls.EXPECTED_HOUSEHOLD_MEMBERS = {
# story_name: expected_member_count
- "Carlos Morales": 5, # head + spouse + 3 children
- "Amina Osman": 4, # head + 3 children
- "Jose Reyes Sr": 8, # head + spouse + 2 adults + 4 children
- "Chen Wei": 7, # head + spouse + 5 children
- "Manuel Santos": 2, # head + spouse
- "James Nguyen": 4, # head + 3 adults
- "David Martinez": 3, # head + spouse + 1 child
+ "Morales": 5, # head + spouse + 3 children
+ "Santos": 5, # head + spouse + 2 children + 1 adult
+ "Dela Cruz": 4, # head + spouse + 2 children
+ "Gutierrez": 7, # head + spouse + 5 children
+ "Castillo": 3, # head + spouse + 1 child
+ "Aquino": 4, # head + 3 children
+ "Reyes": 8, # head + spouse + 2 adults + 4 children
+ "Bautista": 7, # head + spouse + 5 children
+ "Pangilinan": 2, # head + spouse
+ "Navarro": 4, # head + 3 adults
+ "Martinez": 3, # head + spouse + 1 child
}
def _run_generator_for_stories(self):
@@ -512,72 +516,71 @@ def test_household_stories_created_with_members(self):
f"Household '{story_name}' should have {expected_count} members, but has {member_count}",
)
- def test_chen_family_has_all_members(self):
- """Test Chen family has head, spouse, and all 5 children."""
+ def test_bautista_family_has_all_members(self):
+ """Test Bautista family has head, spouse, and all 5 children."""
self._run_generator_for_stories()
group = self.env["res.partner"].search(
[
- ("name", "=", "Chen Wei"),
+ ("name", "=", "Bautista"),
("is_group", "=", True),
],
limit=1,
)
- self.assertTrue(group, "Chen Wei household not found")
+ self.assertTrue(group, "Eduardo Bautista household not found")
memberships = self.env["spp.group.membership"].search([("group", "=", group.id)])
member_names = memberships.mapped("individual.name")
# Expected members - names stored as "FAMILY, GIVEN" uppercase
- # e.g., "Chen Wei" becomes "WEI, CHEN"
- expected_given_names = ["Wei", "Mei", "Ling", "Jun", "Xiao", "Yan", "Bo"]
+ expected_given_names = ["Eduardo", "Carmen", "Patricia", "Fernando", "Lucia", "Rosalie", "Antonio"]
- self.assertEqual(len(memberships), 7, "Chen family should have 7 members")
+ self.assertEqual(len(memberships), 7, "Bautista family should have 7 members")
# Check each expected member exists by given name
for given_name in expected_given_names:
found = any(given_name.upper() in name.upper() for name in member_names)
self.assertTrue(
found,
- f"Expected member with given name '{given_name}' not found in Chen family. "
+ f"Expected member with given name '{given_name}' not found in Bautista family. "
f"Actual members: {member_names}",
)
- def test_nguyen_family_has_all_adults(self):
- """Test Nguyen extended family has head and all 3 adults."""
+ def test_navarro_family_has_all_adults(self):
+ """Test Navarro extended family has head and all 3 adults."""
self._run_generator_for_stories()
group = self.env["res.partner"].search(
[
- ("name", "=", "James Nguyen"),
+ ("name", "=", "Navarro"),
("is_group", "=", True),
],
limit=1,
)
- self.assertTrue(group, "James Nguyen household not found")
+ self.assertTrue(group, "Ricardo Navarro household not found")
memberships = self.env["spp.group.membership"].search([("group", "=", group.id)])
member_names = memberships.mapped("individual.name")
# Expected members - names stored as "FAMILY, GIVEN" uppercase
- expected_given_names = ["James", "Linda", "Michael", "Sarah"]
+ expected_given_names = ["Ricardo", "Lourdes", "Eduardo", "Cristina"]
- self.assertEqual(len(memberships), 4, "Nguyen family should have 4 members")
+ self.assertEqual(len(memberships), 4, "Navarro family should have 4 members")
for given_name in expected_given_names:
found = any(given_name.upper() in name.upper() for name in member_names)
self.assertTrue(
found,
- f"Expected member with given name '{given_name}' not found in Nguyen family. "
+ f"Expected member with given name '{given_name}' not found in Navarro family. "
f"Actual members: {member_names}",
)
def test_existing_empty_group_gets_members_populated(self):
"""Test that existing empty household groups get their members created."""
- # First, delete any existing Chen Wei group to simulate a fresh state
+ # First, delete any existing Bautista group to simulate a fresh state
existing = self.env["res.partner"].search(
[
- ("name", "=", "Chen Wei"),
+ ("name", "=", "Bautista"),
("is_registrant", "=", True),
("is_group", "=", True),
]
@@ -587,10 +590,10 @@ def test_existing_empty_group_gets_members_populated(self):
self.env["spp.group.membership"].search([("group", "in", existing.ids)]).unlink()
existing.unlink()
- # Create an empty Chen Wei group (simulating partial creation bug)
+ # Create an empty Bautista group (simulating partial creation bug)
empty_group = self.env["res.partner"].create(
{
- "name": "Chen Wei",
+ "name": "Bautista",
"is_registrant": True,
"is_group": True,
}
@@ -605,7 +608,7 @@ def test_existing_empty_group_gets_members_populated(self):
# Now check members were created
final_count = self.env["spp.group.membership"].search_count([("group", "=", empty_group.id)])
- self.assertEqual(final_count, 7, "Chen Wei group should have 7 members after generator runs")
+ self.assertEqual(final_count, 7, "Bautista group should have 7 members after generator runs")
def test_head_of_household_has_correct_membership_type(self):
"""Test that household heads have the 'head' membership type."""
@@ -618,7 +621,7 @@ def test_head_of_household_has_correct_membership_type(self):
self.skipTest("Head membership type not configured")
# Check a few households
- for story_name in ["Chen Wei", "James Nguyen", "Carlos Morales"]:
+ for story_name in ["Bautista", "Navarro", "Morales"]:
group = self.env["res.partner"].search(
[
("name", "=", story_name),
@@ -647,10 +650,10 @@ def test_idempotent_member_creation(self):
# Run generator first time
self._run_generator_for_stories()
- # Count members for Chen family
+ # Count members for Bautista family
group = self.env["res.partner"].search(
[
- ("name", "=", "Chen Wei"),
+ ("name", "=", "Bautista"),
("is_group", "=", True),
],
limit=1,
@@ -669,36 +672,36 @@ def test_individual_members_have_correct_attributes(self):
"""Test that created individual members have correct attributes."""
self._run_generator_for_stories()
- # Find Chen Mei (spouse of Chen Wei)
- chen_mei = self.env["res.partner"].search(
+ # Find Carmen Bautista (spouse of Eduardo Bautista)
+ carmen = self.env["res.partner"].search(
[
- ("name", "ilike", "%chen%mei%"),
+ ("name", "ilike", "%carmen%bautista%"),
("is_registrant", "=", True),
("is_group", "=", False),
],
limit=1,
)
- if not chen_mei:
+ if not carmen:
# Try alternative name format
- chen_mei = self.env["res.partner"].search(
+ carmen = self.env["res.partner"].search(
[
- ("name", "ilike", "%mei%chen%"),
+ ("name", "ilike", "%bautista%carmen%"),
("is_registrant", "=", True),
("is_group", "=", False),
],
limit=1,
)
- self.assertTrue(chen_mei, "Chen Mei individual should be created")
- self.assertFalse(chen_mei.is_group, "Chen Mei should not be a group")
- self.assertTrue(chen_mei.is_registrant, "Chen Mei should be a registrant")
+ self.assertTrue(carmen, "Carmen Bautista individual should be created")
+ self.assertFalse(carmen.is_group, "Carmen Bautista should not be a group")
+ self.assertTrue(carmen.is_registrant, "Carmen Bautista should be a registrant")
# Check birthdate is set (age 44 in story)
- if chen_mei.birthdate:
+ if carmen.birthdate:
from datetime import date
- age = (date.today() - chen_mei.birthdate).days // 365
+ age = (date.today() - carmen.birthdate).days // 365
self.assertGreaterEqual(age, 40)
self.assertLessEqual(age, 50)
@@ -706,8 +709,8 @@ def test_get_story_name_returns_correct_names_for_all_stories(self):
"""Test that _get_story_name returns correct names for all demo stories.
This test ensures that story IDs are properly mapped to registrant names,
- preventing duplicate registrants with incorrect names like "Chen Large Family"
- instead of "Chen Wei".
+ preventing duplicate registrants with incorrect names derived from story IDs
+ instead of the actual story name.
"""
from odoo.addons.spp_demo.models import demo_stories
@@ -733,3 +736,137 @@ def test_get_story_name_returns_correct_names_for_all_stories(self):
f"but got '{actual_name}'. This can cause duplicate registrants "
f"with incorrect names.",
)
+
+ # ===================================================================
+ # Compliance manager tests
+ # ===================================================================
+
+ def test_compliance_managers_configured_after_generation(self):
+ """Test that compliance managers have CEL expressions after program creation."""
+ generator = self.env["spp.mis.demo.generator"].create(
+ {
+ "name": "Test Compliance",
+ "create_demo_programs": True,
+ "enroll_demo_stories": False,
+ "generate_volume": False,
+ "create_cycles": False,
+ "locale_origin": self.test_country.id,
+ }
+ )
+ generator.action_generate()
+
+ # Cash Transfer should have compliance CEL
+ cash_transfer = self.env["spp.program"].search([("name", "=", "Cash Transfer Program")], limit=1)
+ self.assertTrue(cash_transfer, "Cash Transfer Program not found")
+ self.assertTrue(cash_transfer.compliance_manager_ids, "No compliance manager on Cash Transfer")
+
+ for wrapper in cash_transfer.compliance_manager_ids:
+ concrete = wrapper.manager_ref_id
+ if hasattr(concrete, "compliance_cel_expression"):
+ self.assertEqual(
+ concrete.compliance_cel_expression,
+ "per_capita_income < poverty_line",
+ )
+ break
+ else:
+ self.fail("No concrete compliance manager found for Cash Transfer")
+
+ def test_conditional_child_grant_compliance_configured(self):
+ """Test that Conditional Child Grant has compliance CEL and program constant."""
+ generator = self.env["spp.mis.demo.generator"].create(
+ {
+ "name": "Test CCG Compliance",
+ "create_demo_programs": True,
+ "enroll_demo_stories": False,
+ "generate_volume": False,
+ "create_cycles": False,
+ "locale_origin": self.test_country.id,
+ }
+ )
+ generator.action_generate()
+
+ ccg = self.env["spp.program"].search([("name", "=", "Conditional Child Grant")], limit=1)
+ self.assertTrue(ccg, "Conditional Child Grant not found")
+
+ # Check compliance expression
+ for wrapper in ccg.compliance_manager_ids:
+ concrete = wrapper.manager_ref_id
+ if hasattr(concrete, "compliance_cel_expression"):
+ self.assertEqual(
+ concrete.compliance_cel_expression,
+ "per_capita_income < income_threshold",
+ )
+ break
+ else:
+ self.fail("No concrete compliance manager found for CCG")
+
+ # Check program constant override
+ param = self.env["spp.cel.program.parameter"].search([("program_id", "=", ccg.id)], limit=1)
+ self.assertTrue(param, "No program parameter found for CCG")
+ self.assertEqual(param.value, "2000")
+
+ def test_non_compliant_cycle_membership_created(self):
+ """Test that Santos has non_compliant cycle membership after generation."""
+ generator = self.env["spp.mis.demo.generator"].create(
+ {
+ "name": "Test Non-Compliant",
+ "create_demo_programs": True,
+ "enroll_demo_stories": True,
+ "create_story_payments": True,
+ "generate_volume": False,
+ "create_cycles": False,
+ "locale_origin": self.test_country.id,
+ }
+ )
+ generator.action_generate()
+
+ # Find Santos household
+ santos = self.env["res.partner"].search(
+ [("name", "=", "Santos"), ("is_group", "=", True), ("is_registrant", "=", True)],
+ limit=1,
+ )
+ if not santos:
+ # Story registrant may not have been created in test environment
+ return
+
+ # Check Cash Transfer cycle membership is non_compliant
+ cash_transfer = self.env["spp.program"].search([("name", "=", "Cash Transfer Program")], limit=1)
+ if not cash_transfer:
+ return
+
+ cycle = self.env["spp.cycle"].search([("program_id", "=", cash_transfer.id)], limit=1)
+ if not cycle:
+ return
+
+ cm = self.env["spp.cycle.membership"].search(
+ [("partner_id", "=", santos.id), ("cycle_id", "=", cycle.id)],
+ limit=1,
+ )
+ self.assertTrue(cm, "No cycle membership found for Santos in Cash Transfer")
+ self.assertEqual(cm.state, "non_compliant", "Santos should be non_compliant")
+
+ def test_programs_without_compliance_have_empty_expression(self):
+ """Programs without compliance_cel_expression should have empty compliance managers."""
+ generator = self.env["spp.mis.demo.generator"].create(
+ {
+ "name": "Test No Compliance",
+ "create_demo_programs": True,
+ "enroll_demo_stories": False,
+ "generate_volume": False,
+ "create_cycles": False,
+ "locale_origin": self.test_country.id,
+ }
+ )
+ generator.action_generate()
+
+ # Food Assistance should NOT have a compliance expression
+ food = self.env["spp.program"].search([("name", "=", "Food Assistance")], limit=1)
+ self.assertTrue(food, "Food Assistance not found")
+
+ for wrapper in food.compliance_manager_ids:
+ concrete = wrapper.manager_ref_id
+ if hasattr(concrete, "compliance_cel_expression"):
+ self.assertFalse(
+ concrete.compliance_cel_expression,
+ "Food Assistance should not have a compliance expression",
+ )
diff --git a/spp_mis_demo_v2/views/mis_demo_wizard_view.xml b/spp_mis_demo_v2/views/mis_demo_wizard_view.xml
index 696da44b..ad31927a 100644
--- a/spp_mis_demo_v2/views/mis_demo_wizard_view.xml
+++ b/spp_mis_demo_v2/views/mis_demo_wizard_view.xml
@@ -31,7 +31,7 @@
- 6 social protection programs (Child Grant, Pension, Emergency Relief, Cash Transfer, Disability Support, Food Assistance)
+ >7 social protection programs (Child Grant, Conditional Child Grant, Pension, Emergency Relief, Cash Transfer, Disability Support, Food Assistance)
- 8 demo personas with full stories and payment history
- 50:
+ expr = expr[:47] + "..."
+ summary_parts.append(f"CEL: {expr}")
else:
# Fallback to other summary details
if hasattr(manager, "admin_area_ids") and manager.admin_area_ids: