import math class ParticipationAdoptionIndex: def __init__(self, num_participants, target_population, feedback_volume): self.num_participants = num_participants self.target_population = target_population self.feedback_volume = feedback_volume self.components_1 = {} self.components_2 = {} # ---------- helper function(s) ---------- def _compute_pci(self, participants_by_group): total = sum(participants_by_group.values()) if total == 0: return 0.0 shares = [ count / total for count in participants_by_group.values() ] return sum(s ** 2 for s in shares) # ---------- public API ---------- def compute_pai(self, participants_by_group=None): # input validation for 'participants_by_group' self.num_participants = sum(participants_by_group.values()) if participants_by_group else num_participants # absolute counts spi_a_abs = self.target_population - self.num_participants spi_b_abs = self.num_participants - self.feedback_volume # ----- 'Participation Dynamics' components ----- pcr = self.num_participants / self.target_population spi_a = spi_a_abs / self.target_population spi_b = spi_b_abs / self.num_participants peg = spi_b_abs / self.target_population # ----- 'Reach and Equity' components ----- pci = self._compute_pci(participants_by_group) if participants_by_group else 0.0 effective_groups = 1.0 / pci if pci > 0 else 0.0 effective_groups_int = int(math.floor(effective_groups)) effective_groups_rounded = int(round(effective_groups)) # ----- percent-based values ----- final_pcr = round(pcr * 100, 2) final_spi_a = round(spi_a * 100, 2) final_spi_b = round(spi_b * 100, 2) final_peg = round((pcr - peg) / pcr * 100 if pcr > 0 else 0.0, 2) final_pci = round(pci, 2) # ----- pci pivot value ----- pci_mid = 0.5 # --------------------------- self.components_1 = { "PCR (Participation Coverage Ratio)": {"Value": final_pcr, "Description": f"{final_pcr}% of the target population have participated."}, "SPI-A (Silent Participation Inference - Silent Non-Adoption)": {"Value": final_spi_a, "Description": f"{final_spi_a}% of the target population were not reached or excluded."}, "SPI-B (Silent Participation Inference - Silent Adoption)": {"Value": final_spi_b, "Description": f"{final_spi_b}% of the participants have participated silently."}, "PEG (Participation-to-Expression Gap)": {"Value": final_peg, "Description": f"There is a {final_peg}% gap between participation and expressed feedback."} } self.components_2 = { "PCI (Participation Concentration Index)": {"Value": final_pci, "Description": ( f"A PCI of {final_pci} suggests " f"{'high' if final_pci >= pci_mid else 'low'} participation concentration, " f"{'dominated by fewer groups' if final_pci >= pci_mid else 'with participation spread across groups'}." )}, "Group Summary": {"Minimum Participating Groups": f"At least {effective_groups_int} groups meaningfully participated.", "Effective Participation Groups": f"Participation is roughly equivalent to {effective_groups_rounded} equally sized groups." } } return self.components_1, self.components_2