This commit is contained in:
2025-11-15 07:09:56 +02:00
parent 70d581fb57
commit 8a7987b9c3
4 changed files with 645 additions and 4 deletions

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import incubaid.herolib.hero.heromodels
// Initialize database
mut mydb := heromodels.new()!
// Create goals
mut goals := [
heromodels.Goal{
id: 'G1'
title: 'Faster Requirements'
description: 'Reduce PRD creation time to under 1 day'
gtype: .product
}
]
// Create use cases
mut use_cases := [
heromodels.UseCase{
id: 'UC1'
title: 'Generate PRD'
actor: 'Product Manager'
goal: 'Create validated PRD'
steps: ['Select template', 'Fill fields', 'Export to Markdown']
success: 'Complete PRD generated'
failure: 'Validation failed'
}
]
// Create requirements
mut criterion := heromodels.AcceptanceCriterion{
id: 'AC1'
description: 'Display template list'
condition: 'List contains >= 5 templates'
}
mut requirements := [
heromodels.Requirement{
id: 'R1'
category: 'Editor'
title: 'Template Selection'
rtype: .functional
description: 'User can select from templates'
priority: .high
criteria: [criterion]
dependencies: []
}
]
// Create constraints
mut constraints := [
heromodels.Constraint{
id: 'C1'
title: 'ARM64 Support'
description: 'Must run on ARM64 infrastructure'
ctype: .technica
}
]
// Create risks
mut risks := map[string]string{}
risks['RISK1'] = 'Templates too limited Add community contributions'
risks['RISK2'] = 'AI suggestions inaccurate Add review workflow'
// Create a new PRD object
mut prd := mydb.prd.new(
product_name: 'Lumina PRD Builder'
version: 'v1.0'
overview: 'Tool to create structured PRDs quickly'
vision: 'Enable teams to generate clear requirements in minutes'
goals: goals
use_cases: use_cases
requirements: requirements
constraints: constraints
risks: risks
)!
// Save to database
prd = mydb.prd.set(prd)!
println(' Created PRD with ID: ${prd.id}')
// Retrieve from database
mut retrieved := mydb.prd.get(prd.id)!
println(' Retrieved PRD: ${retrieved.product_name}')
// List all PRDs
mut all_prds := mydb.prd.list()!
println(' Total PRDs in database: ${all_prds.len}')
// Check if exists
exists := mydb.prd.exist(prd.id)!
println(' PRD exists: ${exists}')

View File

@@ -31,6 +31,7 @@ pub mut:
registration_desk DBRegistrationDesk
messages DBMessages
tags DBTags
prd DBPrd
rpc_handler &Handler
}
@@ -91,6 +92,9 @@ pub fn new(args NewArgs) !&ModelsFactory {
tags: DBTags{
db: &mydb
}
prd: DBPrd{
db: &mydb
}
rpc_handler: &h
}

View File

@@ -1,10 +1,19 @@
module prd
module heromodels
import incubaid.herolib.data.encoder
import incubaid.herolib.data.ourtime
import incubaid.herolib.hero.db
import incubaid.herolib.schemas.jsonrpc { Response, new_error, new_response, new_response_false, new_response_int, new_response_true }
import incubaid.herolib.hero.user { UserRef }
import json
// Basic enums for clarity
// Core PRD type, this is the root object
@[heap]
pub struct ProductRequirementsDoc {
pub:
db.Base
pub mut:
product_name string
version string
overview string
@@ -13,9 +22,12 @@ pub:
use_cases []UseCase
requirements []Requirement
constraints []Constraint
risks map[string]string // risk_id -> mitigation
}
pub struct DBPrd {
pub mut:
db &db.DB @[skip; str: skip]
}
pub enum PRDPriority {
low
@@ -87,7 +99,7 @@ pub enum ConstraintType {
design
}
pub struct constraint {
pub struct Constraint {
pub:
id string
title string
@@ -95,3 +107,309 @@ pub:
ctype ConstraintType
}
pub fn (self ProductRequirementsDoc) type_name() string {
return 'prd'
}
pub fn (self ProductRequirementsDoc) description(methodname string) string {
match methodname {
'set' {
return 'Create or update a product requirements document. Returns the ID of the PRD.'
}
'get' {
return 'Retrieve a PRD by ID. Returns the complete PRD object.'
}
'delete' {
return 'Delete a PRD by ID. Returns true if successful.'
}
'exist' {
return 'Check if a PRD exists by ID. Returns true or false.'
}
'list' {
return 'List all PRDs. Returns an array of PRD objects.'
}
else {
return 'Generic method for PRD operations.'
}
}
}
pub fn (self ProductRequirementsDoc) example(methodname string) (string, string) {
match methodname {
'set' {
return '{"product_name": "Test Product", "version": "v1.0", "overview": "A test product", "vision": "To test the system", "goals": [], "use_cases": [], "requirements": [], "constraints": []}', '1'
}
'get' {
return '{"id": 1}', '{"product_name": "Test Product", "version": "v1.0", "overview": "A test product", "vision": "To test the system", "goals": [], "use_cases": [], "requirements": [], "constraints": []}'
}
'delete' {
return '{"id": 1}', 'true'
}
'exist' {
return '{"id": 1}', 'true'
}
'list' {
return '{}', '[{"product_name": "Test Product", "version": "v1.0"}]'
}
else {
return '{}', '{}'
}
}
}
pub fn (self ProductRequirementsDoc) dump(mut e encoder.Encoder) ! {
e.add_string(self.product_name)
e.add_string(self.version)
e.add_string(self.overview)
e.add_string(self.vision)
// Encode goals array
e.add_u16(u16(self.goals.len))
for goal in self.goals {
e.add_string(goal.id)
e.add_string(goal.title)
e.add_string(goal.description)
e.add_u8(u8(goal.gtype))
}
// Encode use_cases array
e.add_u16(u16(self.use_cases.len))
for uc in self.use_cases {
e.add_string(uc.id)
e.add_string(uc.title)
e.add_string(uc.actor)
e.add_string(uc.goal)
e.add_list_string(uc.steps)
e.add_string(uc.success)
e.add_string(uc.failure)
}
// Encode requirements array
e.add_u16(u16(self.requirements.len))
for req in self.requirements {
e.add_string(req.id)
e.add_string(req.category)
e.add_string(req.title)
e.add_u8(u8(req.rtype))
e.add_string(req.description)
e.add_u8(u8(req.priority))
// Encode acceptance criteria
e.add_u16(u16(req.criteria.len))
for criterion in req.criteria {
e.add_string(criterion.id)
e.add_string(criterion.description)
e.add_string(criterion.condition)
}
// Encode dependencies
e.add_list_string(req.dependencies)
}
// Encode constraints array
e.add_u16(u16(self.constraints.len))
for constraint in self.constraints {
e.add_string(constraint.id)
e.add_string(constraint.title)
e.add_string(constraint.description)
e.add_u8(u8(constraint.ctype))
}
}
pub fn (mut self DBPrd) load(mut o ProductRequirementsDoc, mut e encoder.Decoder) ! {
o.product_name = e.get_string()!
o.version = e.get_string()!
o.overview = e.get_string()!
o.vision = e.get_string()!
// Decode goals
goals_len := e.get_u16()!
mut goals := []Goal{}
for _ in 0 .. goals_len {
goals << Goal{
id: e.get_string()!
title: e.get_string()!
description: e.get_string()!
gtype: unsafe { GoalType(e.get_u8()!) }
}
}
o.goals = goals
// Decode use_cases
use_cases_len := e.get_u16()!
mut use_cases := []UseCase{}
for _ in 0 .. use_cases_len {
use_cases << UseCase{
id: e.get_string()!
title: e.get_string()!
actor: e.get_string()!
goal: e.get_string()!
steps: e.get_list_string()!
success: e.get_string()!
failure: e.get_string()!
}
}
o.use_cases = use_cases
// Decode requirements
requirements_len := e.get_u16()!
mut requirements := []Requirement{}
for _ in 0 .. requirements_len {
req_id := e.get_string()!
req_category := e.get_string()!
req_title := e.get_string()!
req_rtype := unsafe { RequirementType(e.get_u8()!) }
req_description := e.get_string()!
req_priority := unsafe { PRDPriority(e.get_u8()!) }
// Decode criteria
criteria_len := e.get_u16()!
mut criteria := []AcceptanceCriterion{}
for _ in 0 .. criteria_len {
criteria << AcceptanceCriterion{
id: e.get_string()!
description: e.get_string()!
condition: e.get_string()!
}
}
// Decode dependencies
dependencies := e.get_list_string()!
requirements << Requirement{
id: req_id
category: req_category
title: req_title
rtype: req_rtype
description: req_description
priority: req_priority
criteria: criteria
dependencies: dependencies
}
}
o.requirements = requirements
// Decode constraints
constraints_len := e.get_u16()!
mut constraints := []Constraint{}
for _ in 0 .. constraints_len {
constraints << Constraint{
id: e.get_string()!
title: e.get_string()!
description: e.get_string()!
ctype: unsafe { ConstraintType(e.get_u8()!) }
}
}
o.constraints = constraints
}
@[params]
pub struct PrdArg {
pub mut:
id u32
product_name string @[required]
version string
overview string
vision string
goals []Goal
use_cases []UseCase
requirements []Requirement
constraints []Constraint
securitypolicy u32
tags []string
}
pub fn (mut self DBPrd) new(args PrdArg) !ProductRequirementsDoc {
mut o := ProductRequirementsDoc{
product_name: args.product_name
version: args.version
overview: args.overview
vision: args.vision
goals: args.goals
use_cases: args.use_cases
requirements: args.requirements
constraints: args.constraints
updated_at: ourtime.now().unix()
}
o.securitypolicy = args.securitypolicy
o.tags = self.db.tags_get(args.tags)!
return o
}
pub fn (mut self DBPrd) set(o ProductRequirementsDoc) !ProductRequirementsDoc {
return self.db.set[ProductRequirementsDoc](o)!
}
pub fn (mut self DBPrd) delete(id u32) !bool {
if !self.db.exists[ProductRequirementsDoc](id)! {
return false
}
self.db.delete[ProductRequirementsDoc](id)!
return true
}
pub fn (mut self DBPrd) exist(id u32) !bool {
return self.db.exists[ProductRequirementsDoc](id)!
}
pub fn (mut self DBPrd) get(id u32) !ProductRequirementsDoc {
mut o, data := self.db.get_data[ProductRequirementsDoc](id)!
mut e_decoder := encoder.decoder_new(data)
self.load(mut o, mut e_decoder)!
return o
}
pub fn (mut self DBPrd) list() ![]ProductRequirementsDoc {
return self.db.list[ProductRequirementsDoc]()!.map(self.get(it)!)
}
pub fn prd_handle(mut f ModelsFactory, rpcid int, servercontext map[string]string, userref UserRef, method string, params string) !Response {
match method {
'get' {
id := db.decode_u32(params)!
res := f.prd.get(id)!
return new_response(rpcid, json.encode(res))
}
'set' {
mut args := db.decode_generic[PrdArg](params)!
mut o := f.prd.new(args)!
if args.id != 0 {
o.id = args.id
}
o = f.prd.set(o)!
return new_response_int(rpcid, int(o.id))
}
'delete' {
id := db.decode_u32(params)!
deleted := f.prd.delete(id)!
if deleted {
return new_response_true(rpcid)
} else {
return new_error(rpcid,
code: 404
message: 'PRD with ID ${id} not found'
)
}
}
'exist' {
id := db.decode_u32(params)!
if f.prd.exist(id)! {
return new_response_true(rpcid)
} else {
return new_response_false(rpcid)
}
}
'list' {
res := f.prd.list()!
return new_response(rpcid, json.encode(res))
}
else {
return new_error(rpcid,
code: 32601
message: 'Method ${method} not found on prd'
)
}
}
}

View File

@@ -0,0 +1,226 @@
module heromodels
import incubaid.herolib.hero.db
fn test_prd_new() ! {
mut mydb := db.new_test()!
mut db_prd := DBPrd{
db: &mydb
}
mut args := PrdArg{
product_name: 'Test Product'
version: 'v1.0'
overview: 'This is a test product.'
vision: 'To revolutionize testing.'
goals: []
use_cases: []
requirements: []
constraints: []
risks: {}
}
prd := db_prd.new(args)!
assert prd.product_name == 'Test Product'
assert prd.version == 'v1.0'
assert prd.overview == 'This is a test product.'
assert prd.vision == 'To revolutionize testing.'
assert prd.goals.len == 0
assert prd.use_cases.len == 0
assert prd.requirements.len == 0
assert prd.constraints.len == 0
assert prd.risks.len == 0
assert prd.updated_at > 0
println(' PRD new test passed!')
}
fn test_prd_crud_operations() ! {
mut mydb := db.new_test()!
mut db_prd := DBPrd{
db: &mydb
}
// Create a new PRD
mut args := PrdArg{
product_name: 'CRUD Test Product'
version: 'v1.0'
overview: 'This is a test product for CRUD.'
vision: 'To test CRUD operations.'
goals: []
use_cases: []
requirements: []
constraints: []
risks: {}
}
mut prd := db_prd.new(args)!
prd = db_prd.set(prd)!
original_id := prd.id
// Test get
retrieved_prd := db_prd.get(original_id)!
assert retrieved_prd.product_name == 'CRUD Test Product'
assert retrieved_prd.version == 'v1.0'
assert retrieved_prd.id == original_id
// Test exist
exists := db_prd.exist(original_id)!
assert exists == true
// Test delete
db_prd.delete(original_id)!
exists_after_delete := db_prd.exist(original_id)!
assert exists_after_delete == false
println(' PRD CRUD operations test passed!')
}
fn test_prd_encoding_decoding_complex() ! {
mut mydb := db.new_test()!
mut db_prd := DBPrd{
db: &mydb
}
mut goal := Goal{
id: 'G1'
title: 'Speed'
description: 'Generate PRDs in minutes'
gtype: .product
}
mut use_case := UseCase{
id: 'UC1'
title: 'Create PRD'
actor: 'Product Manager'
goal: 'Produce PRD quickly'
steps: ['Click new', 'Fill data', 'Export']
success: 'Valid PRD generated'
failure: 'Missing fields'
}
mut criterion := AcceptanceCriterion{
id: 'AC1'
description: 'System displays template list'
condition: 'List contains >= 5 templates'
}
mut requirement := Requirement{
id: 'R1'
category: 'Editor'
title: 'Template Selection'
rtype: .functional
description: 'User can select from predefined templates'
priority: .high
criteria: [criterion]
dependencies: []
}
mut constraint := Constraint{
id: 'C1'
title: 'ARM64 Only'
description: 'Must run on ARM64 servers'
ctype: .technica
}
mut risks := map[string]string{}
risks['RISK1'] = 'Mitigation strategy here'
mut args := PrdArg{
product_name: 'Complex Test Product'
version: 'v2.0'
overview: 'Complete test with all fields'
vision: 'Full feature test'
goals: [goal]
use_cases: [use_case]
requirements: [requirement]
constraints: [constraint]
risks: risks
}
mut prd := db_prd.new(args)!
prd = db_prd.set(prd)!
prd_id := prd.id
// Retrieve and verify
retrieved_prd := db_prd.get(prd_id)!
assert retrieved_prd.product_name == 'Complex Test Product'
assert retrieved_prd.goals.len == 1
assert retrieved_prd.goals[0].id == 'G1'
assert retrieved_prd.goals[0].gtype == .product
assert retrieved_prd.use_cases.len == 1
assert retrieved_prd.use_cases[0].id == 'UC1'
assert retrieved_prd.use_cases[0].steps.len == 3
assert retrieved_prd.requirements.len == 1
assert retrieved_prd.requirements[0].id == 'R1'
assert retrieved_prd.requirements[0].criteria.len == 1
assert retrieved_prd.requirements[0].priority == .high
assert retrieved_prd.constraints.len == 1
assert retrieved_prd.constraints[0].id == 'C1'
assert retrieved_prd.constraints[0].ctype == .technica
assert retrieved_prd.risks.len == 1
assert retrieved_prd.risks['RISK1'] == 'Mitigation strategy here'
println(' PRD encoding/decoding complex test passed!')
}
fn test_prd_type_name() ! {
mut mydb := db.new_test()!
mut db_prd := DBPrd{
db: &mydb
}
mut args := PrdArg{
product_name: 'Type Name Test'
version: 'v1.0'
overview: 'Test'
vision: 'Test'
goals: []
use_cases: []
requirements: []
constraints: []
risks: {}
}
prd := db_prd.new(args)!
type_name := prd.type_name()
assert type_name == 'prd'
println(' PRD type_name test passed!')
}
fn test_prd_list() ! {
mut mydb := db.new_test()!
mut db_prd := DBPrd{
db: &mydb
}
// Create multiple PRDs
for i in 0 .. 3 {
mut args := PrdArg{
product_name: 'Product ${i}'
version: 'v1.0'
overview: 'Overview ${i}'
vision: 'Vision ${i}'
goals: []
use_cases: []
requirements: []
constraints: []
risks: {}
}
mut prd := db_prd.new(args)!
prd = db_prd.set(prd)!
}
// List all PRDs
all_prds := db_prd.list()!
assert all_prds.len == 3
println(' PRD list test passed!')
}