From 0a2466cd2660b95d719ce0f053a3cd0cdae69390 Mon Sep 17 00:00:00 2001 From: coodos Date: Wed, 1 Apr 2026 15:02:49 +0530 Subject: [PATCH 1/4] feat: meritocratic voting prompt --- .../api/src/controllers/WebhookController.ts | 18 ++++++++------ .../api/src/database/entities/Poll.ts | 3 +++ .../1775035663491-add-custom-prompt.ts | 13 ++++++++++ .../src/services/VotingReputationService.ts | 10 ++++---- .../web3adapter/mappings/poll.mapping.json | 1 + .../api/src/controllers/PollController.ts | 9 +++---- .../evoting/api/src/database/entities/Poll.ts | 3 +++ .../1775035663491-add-custom-prompt.ts | 13 ++++++++++ .../evoting/api/src/services/PollService.ts | 4 +++- .../web3adapter/mappings/poll.mapping.json | 1 + .../client/src/app/(app)/create/page.tsx | 24 +++++++++++++++---- platforms/evoting/client/src/lib/pollApi.ts | 2 ++ 12 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 platforms/ereputation/api/src/database/migrations/1775035663491-add-custom-prompt.ts create mode 100644 platforms/evoting/api/src/database/migrations/1775035663491-add-custom-prompt.ts diff --git a/platforms/ereputation/api/src/controllers/WebhookController.ts b/platforms/ereputation/api/src/controllers/WebhookController.ts index bb2e269b2..61e3d34ab 100644 --- a/platforms/ereputation/api/src/controllers/WebhookController.ts +++ b/platforms/ereputation/api/src/controllers/WebhookController.ts @@ -257,11 +257,12 @@ export class WebhookController { poll.mode = local.data.mode as "normal" | "point" | "rank"; poll.visibility = local.data.visibility as "public" | "private"; poll.votingWeight = (local.data.votingWeight || "1p1v") as "1p1v" | "ereputation"; - poll.options = Array.isArray(local.data.options) - ? local.data.options + poll.options = Array.isArray(local.data.options) + ? local.data.options : (local.data.options as string).split(","); poll.deadline = local.data.deadline ? new Date(local.data.deadline as string) : null; poll.groupId = groupId; + poll.customPrompt = (local.data.customPrompt as string) || null; await pollRepository.save(poll); finalLocalId = poll.id; @@ -280,11 +281,12 @@ export class WebhookController { mode: local.data.mode as "normal" | "point" | "rank", visibility: local.data.visibility as "public" | "private", votingWeight: (local.data.votingWeight || "1p1v") as "1p1v" | "ereputation", - options: Array.isArray(local.data.options) - ? local.data.options + options: Array.isArray(local.data.options) + ? local.data.options : (local.data.options as string).split(","), deadline: local.data.deadline ? new Date(local.data.deadline as string) : null, - groupId: groupId + groupId: groupId, + customPrompt: (local.data.customPrompt as string) || null }); const savedPoll = await pollRepository.save(poll); @@ -384,10 +386,12 @@ export class WebhookController { const group = await this.groupService.getGroupById(poll.groupId); if (!group) return; - const charter = (group.charter && group.charter.trim()) ? group.charter : ""; + const evaluationCriteria = (poll.customPrompt && poll.customPrompt.trim()) + ? poll.customPrompt + : (group.charter && group.charter.trim()) ? group.charter : ""; const reputationResults = await this.votingReputationService.calculateGroupMemberReputations( poll.groupId, - charter + evaluationCriteria ); const voteReputationResult = await this.votingReputationService.saveReputationResults( diff --git a/platforms/ereputation/api/src/database/entities/Poll.ts b/platforms/ereputation/api/src/database/entities/Poll.ts index 63cdcee0d..3e113ca1a 100644 --- a/platforms/ereputation/api/src/database/entities/Poll.ts +++ b/platforms/ereputation/api/src/database/entities/Poll.ts @@ -46,6 +46,9 @@ export class Poll { @Column("uuid", { nullable: true }) groupId!: string | null; // Group this poll belongs to + @Column("text", { nullable: true }) + customPrompt!: string | null; + @OneToMany( () => Vote, (vote) => vote.poll, diff --git a/platforms/ereputation/api/src/database/migrations/1775035663491-add-custom-prompt.ts b/platforms/ereputation/api/src/database/migrations/1775035663491-add-custom-prompt.ts new file mode 100644 index 000000000..dd5f28eca --- /dev/null +++ b/platforms/ereputation/api/src/database/migrations/1775035663491-add-custom-prompt.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddCustomPrompt1775035663491 implements MigrationInterface { + name = 'AddCustomPrompt1775035663491' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "polls" ADD "customPrompt" text`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "polls" DROP COLUMN "customPrompt"`); + } +} diff --git a/platforms/ereputation/api/src/services/VotingReputationService.ts b/platforms/ereputation/api/src/services/VotingReputationService.ts index 124ce16e8..16892ff5e 100644 --- a/platforms/ereputation/api/src/services/VotingReputationService.ts +++ b/platforms/ereputation/api/src/services/VotingReputationService.ts @@ -401,19 +401,19 @@ ${refsText}`; return ` You are analyzing the reputation of multiple users for voting purposes within a group. -GROUP CHARTER: +EVALUATION CRITERIA: ${charter} USERS AND THEIR REFERENCES: ${membersCSV} TASK: -Based on the group charter and the references provided, calculate a reputation score from 1-5 for EACH user that will be used for weighted voting. +Based on the evaluation criteria and the references provided, calculate a reputation score from 1-5 for EACH user that will be used for weighted voting. -IMPORTANT: +IMPORTANT: - Each score must be between 1 and 5 (inclusive) -- Consider how well the references align with the group's charter and values -- Focus on voting-relevant reputation factors mentioned in the charter +- Consider how well the references align with the evaluation criteria and values +- Focus on voting-relevant reputation factors mentioned in the evaluation criteria - Provide a ONE SENTENCE justification explaining each score Respond with a JSON array in this exact format: diff --git a/platforms/ereputation/api/src/web3adapter/mappings/poll.mapping.json b/platforms/ereputation/api/src/web3adapter/mappings/poll.mapping.json index 6f617d78e..0fdee35f1 100644 --- a/platforms/ereputation/api/src/web3adapter/mappings/poll.mapping.json +++ b/platforms/ereputation/api/src/web3adapter/mappings/poll.mapping.json @@ -11,6 +11,7 @@ "deadline": "deadline", "creatorId": "creatorId", "group": "groups(group.id),group", + "customPrompt": "customPrompt", "createdAt": "createdAt", "updatedAt": "updatedAt" }, diff --git a/platforms/evoting/api/src/controllers/PollController.ts b/platforms/evoting/api/src/controllers/PollController.ts index e5d1f26aa..f59dce7ac 100644 --- a/platforms/evoting/api/src/controllers/PollController.ts +++ b/platforms/evoting/api/src/controllers/PollController.ts @@ -53,10 +53,10 @@ export class PollController { createPoll = async (req: Request, res: Response) => { try { console.log('🔍 Full request body:', req.body); - const { title, mode, visibility, votingWeight, options, deadline, groupId } = req.body; + const { title, mode, visibility, votingWeight, options, deadline, groupId, customPrompt } = req.body; const creatorId = (req as any).user.id; - - console.log('🔍 Extracted data:', { title, mode, visibility, votingWeight, options, deadline, groupId, creatorId }); + + console.log('🔍 Extracted data:', { title, mode, visibility, votingWeight, options, deadline, groupId, customPrompt, creatorId }); console.log('🔍 groupId type:', typeof groupId, 'value:', groupId); // groupId is optional - only required for system messages @@ -69,7 +69,8 @@ export class PollController { options, deadline, creatorId, - groupId + groupId, + customPrompt }); console.log('🔍 Created poll:', poll); diff --git a/platforms/evoting/api/src/database/entities/Poll.ts b/platforms/evoting/api/src/database/entities/Poll.ts index 1cf453984..e305cd725 100644 --- a/platforms/evoting/api/src/database/entities/Poll.ts +++ b/platforms/evoting/api/src/database/entities/Poll.ts @@ -62,6 +62,9 @@ export class Poll { @Column("uuid", { nullable: true }) groupId!: string | null; // Group this poll belongs to + @Column("text", { nullable: true }) + customPrompt!: string | null; + @OneToMany( () => Vote, (vote) => vote.poll, diff --git a/platforms/evoting/api/src/database/migrations/1775035663491-add-custom-prompt.ts b/platforms/evoting/api/src/database/migrations/1775035663491-add-custom-prompt.ts new file mode 100644 index 000000000..dd5f28eca --- /dev/null +++ b/platforms/evoting/api/src/database/migrations/1775035663491-add-custom-prompt.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddCustomPrompt1775035663491 implements MigrationInterface { + name = 'AddCustomPrompt1775035663491' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "polls" ADD "customPrompt" text`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "polls" DROP COLUMN "customPrompt"`); + } +} diff --git a/platforms/evoting/api/src/services/PollService.ts b/platforms/evoting/api/src/services/PollService.ts index b1c505b0d..d8f8a241d 100644 --- a/platforms/evoting/api/src/services/PollService.ts +++ b/platforms/evoting/api/src/services/PollService.ts @@ -181,6 +181,7 @@ export class PollService { deadline?: string; creatorId: string; groupId?: string; // Optional groupId for system messages + customPrompt?: string; }): Promise { console.log('🔍 PollService.createPoll called with:', pollData); @@ -229,7 +230,8 @@ export class PollService { deadline: hasDeadline ? new Date(pollData.deadline!) : null, creator, creatorId: pollData.creatorId, - groupId: pollData.groupId || null + groupId: pollData.groupId || null, + customPrompt: pollData.customPrompt || null }; console.log('🔍 Creating poll entity with data:', pollDataForEntity); diff --git a/platforms/evoting/api/src/web3adapter/mappings/poll.mapping.json b/platforms/evoting/api/src/web3adapter/mappings/poll.mapping.json index db4752825..ff9f2c345 100644 --- a/platforms/evoting/api/src/web3adapter/mappings/poll.mapping.json +++ b/platforms/evoting/api/src/web3adapter/mappings/poll.mapping.json @@ -12,6 +12,7 @@ "deadline": "deadline", "creatorId": "creatorId", "group": "groups(group.id),group", + "customPrompt": "customPrompt", "createdAt": "createdAt", "updatedAt": "updatedAt" }, diff --git a/platforms/evoting/client/src/app/(app)/create/page.tsx b/platforms/evoting/client/src/app/(app)/create/page.tsx index 9753d85ff..6a5d06b6e 100644 --- a/platforms/evoting/client/src/app/(app)/create/page.tsx +++ b/platforms/evoting/client/src/app/(app)/create/page.tsx @@ -34,6 +34,7 @@ const createPollSchema = z.object({ return true; }, "Please select a valid group"), votingWeight: z.enum(["1p1v", "ereputation"]).default("1p1v"), + customPrompt: z.string().optional(), options: z .array(z.string() .min(1, "Option cannot be empty") @@ -74,6 +75,7 @@ export default function CreatePoll() { visibility: "public", groupId: "", votingWeight: "1p1v", + customPrompt: "", options: ["", ""], deadline: "", }, @@ -219,7 +221,8 @@ export default function CreatePoll() { votingWeight: data.votingWeight, groupId: data.groupId, options: data.options.filter(option => option.trim() !== ""), - ...(utcDeadline ? { deadline: utcDeadline } : {}) + ...(utcDeadline ? { deadline: utcDeadline } : {}), + ...(data.customPrompt?.trim() ? { customPrompt: data.customPrompt.trim() } : {}) }); toast({ @@ -597,9 +600,22 @@ export default function CreatePoll() { {watchedVotingWeight === "ereputation" && ( -

- Votes will be weighted by each voter's eReputation score. -

+
+

+ Votes will be weighted by each voter's eReputation score. +

+ +