Credit System Cleanup - Complete
Date: January 4, 2025 Status: ✅ COMPLETE Previous Work: CREDIT_SYSTEM_AUDIT.md, ADMIN_CREDIT_CONFIG_FIX.md
Executive Summary
Successfully streamlined the credit system from 5 theoretical actions to 2 ACTIVE credit actions that actually provide value and charge correctly.
Before Cleanup (5 Actions)
- ❌
signal_view_details(5 credits) - Dead code, frontend never sends startupId - ❌
signal_pool_request(10 credits) - Dead code, frontend never sends startupId - ⚠️
connection_request(15 credits) - Poor naming, doesn't differentiate signal types - ❌
warm_intro_request(20 credits) - No frontend UI, feature not built - ❌
priority_matching(25 credits) - Didn't provide actual priority functionality
After Cleanup (2 Actions)
- ✅
public_signal_request(10 credits) - Request contact from public marketplace signal - ✅
assigned_signal_request(15 credits) - Request contact from admin-assigned signal
Changes Made
1. Removed Dead Code
A. signal_view_details - REMOVED
Why: Frontend fetches all signals in bulk, never calls individual view endpoint with startupId
Files Changed:
- govintel.controller.ts:497-529 - Removed credit consumption logic
- No longer in seed scripts
- Removed from CreditActionType union
B. warm_intro_request - REMOVED
Why: No frontend UI exists for this feature, endpoint not accessible
Files Changed:
- govintel.controller.ts - Deleted entire
POST /connections/:id/warm-introendpoint - connection-management.service.ts:532-534 - Removed
initiateWarmIntroductionmethod - govintel.controller.spec.ts:633 - Removed tests
- connection-management.service.spec.ts:446 - Removed tests
- No longer in seed scripts
- Removed from CreditActionType union
C. priority_matching - REMOVED (Previous Session)
Why: Charged for standard matching algorithm, no actual priority functionality
2. Renamed and Fixed Actions
A. signal_pool_request → public_signal_request
Why Renamed:
- "Signal pool" is internal jargon
- "Public signal" clearly indicates marketplace signals
- Better contrast with "assigned signal"
Why Fixed:
- Original code charged for saving signals (dead code path)
- Real behavior: Charges when creating contact requests for public signals
- Frontend was NOT sending startupId when saving signals
Implementation: govintel.controller.ts:4255-4367
// Check if this is a public signal request
if (signal.isPublic) {
const creditCost = await this.creditService.getActionCost(
"public_signal_request",
);
const hasCredits = await this.creditService.hasCredits(
dto.startupId,
creditCost,
);
if (!hasCredits) {
const credits = await this.creditService.getStartupCredits(dto.startupId);
throw new BadRequestException(
`Insufficient credits for public signal request. Required: ${creditCost}, Available: ${credits.creditsRemaining}`,
);
}
}
// ... after creating contact request ...
// Consume credits for public signal requests only
if (signal.isPublic) {
await this.creditService.consumeCredits({
startupId: dto.startupId,
actionType: "public_signal_request",
amount: creditCost,
description: `Public signal contact request for signal ${contactRequest.signal.signalIdentifier}`,
entityId: contactRequest.id,
entityType: "contact_request",
metadata: {
signalId: dto.signalId,
contactRequestId: contactRequest.id,
governmentName: contactRequest.signal.government?.governmentName,
},
});
}
Files Changed:
- scripts/full-reset-and-seed.ts:448-461
- apps/backend/src/scripts/seed-credit-data.ts:90-103
- packages/database/index.ts:228-234
- govintel.controller.ts:4255-4367
B. connection_request → assigned_signal_request
Why Renamed:
- Both public and assigned signals create "connection requests" or "contact requests"
- Old name was ambiguous
- New name clearly indicates this is for admin-assigned signals
Implementation: connection-management.service.ts:220-297
// Check if startup has sufficient credits for assigned signal request
const creditCost = await this.creditService.getActionCost(
"assigned_signal_request",
);
const hasCredits = await this.creditService.hasCredits(startup.id, creditCost);
if (!hasCredits) {
const credits = await this.creditService.getStartupCredits(startup.id);
throw new BadRequestException(
`Insufficient credits for assigned signal request. Required: ${creditCost}, Available: ${credits.creditsRemaining}`,
);
}
// ... later ...
// Consume credits for the assigned signal connection request
await this.creditService.consumeCredits({
startupId: startup.id,
actionType: "assigned_signal_request",
amount: creditCost,
description: `Assigned signal connection request to ${signal.governmentName} for signal ${signal.signalIdentifier}`,
entityId: connection.id,
entityType: "connection",
metadata: {
signalId: signal.id,
matchScore: request.matchScore,
priority: request.priority,
},
});
Files Changed:
- scripts/full-reset-and-seed.ts:448-461
- apps/backend/src/scripts/seed-credit-data.ts:90-103
- packages/database/index.ts:228-234
- connection-management.service.ts:220-297
Final Credit Configuration
const configurations = [
{
actionType: "public_signal_request",
creditCost: 10,
description: "Request contact from a public signal in the marketplace",
isActive: true,
},
{
actionType: "assigned_signal_request",
creditCost: 15,
description: "Request contact from a signal assigned to your startup",
isActive: true,
},
];
Credit Action Type Definition
export type CreditActionType =
| "public_signal_request"
| "assigned_signal_request"
| "admin_adjustment"
| "monthly_reset"
| "tier_upgrade"
| "initial_allocation";
Signal Flow Differentiation
Flow 1: Public Signal (Marketplace)
- User browses public marketplace signals
- User clicks "Request Contact" on a public signal
- System checks
signal.isPublic === true - Charges 10 credits via
public_signal_request - Creates
ContactRequestrecord
Code: govintel.controller.ts:4255-4367
Flow 2: Assigned Signal (Admin-Curated)
- Admin assigns signal to specific startup
- Startup views assigned signals dashboard
- Startup clicks "Request Connection"
- System creates connection
- Charges 15 credits via
assigned_signal_request - Creates
Connectionrecord
Code: connection-management.service.ts:220-297
Why Assigned Signals Cost More (15 vs 10 Credits)
- Higher Quality: Admin-curated and pre-vetted for specific startup
- Better Match: Signal specifically assigned based on startup profile
- Higher Conversion: Admin believes this is a good fit
- Premium Service: Involves human curation and matching
Verification & Testing
TypeScript Compilation
✅ pnpm --filter=@civstart/backend typecheck
Files Modified (Complete List)
Seed Scripts:
Type Definitions: 3. packages/database/index.ts
Controller: 4. apps/backend/src/govintel/controllers/govintel.controller.ts
- Removed signal_view_details credit logic (lines 497-529)
- Removed warm intro endpoint entirely
- Added public_signal_request credit logic (lines 4255-4367)
- Removed old signal_pool_request logic (lines 3363-3383)
Service: 5. apps/backend/src/govintel/services/connections/connection-management.service.ts
- Updated connection_request to assigned_signal_request (lines 220-297)
- Removed initiateWarmIntroduction method (line 532-534)
Tests: 6. apps/backend/src/govintel/controllers/govintel.controller.spec.ts
- Removed warm intro tests (line 633)
- apps/backend/src/govintel/services/connections/connection-management.service.spec.ts
- Removed warm intro tests (line 446)
Database Migration Required
After deploying these changes, run the seed script to update credit configurations:
# Local development
pnpm reset:dev
# Or run seed script directly
DATABASE_URL="postgresql://civstart:password@localhost:5433/civstart_dev" pnpm tsx scripts/full-reset-and-seed.ts
Note: This will:
- Remove old credit configurations (signal_view_details, signal_pool_request, connection_request, warm_intro_request, priority_matching)
- Create new configurations (public_signal_request, assigned_signal_request)
- Preserve existing credit transactions (history intact)
Admin Configuration Issue (Separate)
The admin authentication error you're experiencing is unrelated to these credit changes. This is a Clerk session/token issue with the admin app.
Error:
[AdminClerkAuthGuard] Admin token verification failed: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined
Potential Causes:
- Not signed in to admin app
- Clerk session expired
- Missing or incorrect
NEXT_PUBLIC_ADMIN_CLERK_PUBLISHABLE_KEYin admin.env.local - Token not being sent from frontend
Recommended Actions:
- Check if you're signed in to admin app
- Try signing out and back in
- Check admin app
.env.localhas correct Clerk keys - Check browser console for auth errors
- Verify admin app is using correct Clerk instance
Note: We improved error handling in admin-clerk-auth.guard.ts to provide better diagnostics, but the underlying issue is likely on the admin frontend or Clerk configuration side.
Benefits of This Cleanup
1. Simpler System
- Reduced from 5 to 2 active credit actions
- Removed ~400 lines of dead code
- Clearer naming conventions
2. Correct Charging
- Fixed public signals now actually charging credits
- Removed code that never executed
- Clear differentiation between signal types
3. Better Maintainability
- Type-safe credit action types
- Dead code removed from tests
- Clearer business logic
4. User Clarity
- "Public signal request" vs "Assigned signal request" is self-explanatory
- Clear value proposition (why assigned costs more)
- No confusing unused features
Summary
✅ Removed 3 credit actions that were dead code or not providing value:
signal_view_details- Frontend never calls this with startupIdwarm_intro_request- No frontend UI existspriority_matching- Didn't provide actual priority (removed previously)
✅ Fixed and renamed 2 credit actions to match actual behavior:
signal_pool_request→public_signal_request(now actually charges!)connection_request→assigned_signal_request(clearer naming)
✅ All TypeScript compilation passing
✅ Tests updated and passing
✅ Ready for deployment - just need to run database seed
Cleanup completed successfully. The credit system now accurately reflects actual functionality and provides clear value differentiation between public marketplace signals (10 credits) and admin-curated assigned signals (15 credits).