Skip to content

Domain Model

fushinryu-model is built around entity types that form a containment hierarchy.

graph TD
    S[Scope] -->|contains| US[UserStory]
    S -->|backlog| T[Task]
    S -->|rule_categories| CAT[Category]
    CAT -->|subcategories| CAT
    CAT -->|ruleset| RS[Ruleset]
    RS --> CMD[Commandment]
    RS --> SUG[Suggestion]
    US -->|contains| AC[AcceptanceCriterion]
    AC -->|contains| V[ValidationEntry\nManualValidation · AutomatedValidation]
    T -. references .-> AC
    S -->|roles| R[Role]
    R -->|parent| R
    R -->|competences| RC[RoleCompetence]
    RC --> C[Competence]
    S -->|role_assignments| RA[RoleAssignment]
    RA --> EMP[Employee]
    RA --> R
    EMP -->|competences| EC[EmployeeCompetence]
    EC --> C
    TP[TrainingProgram] -->|competences| PC[ProgramCompetence]
    PC --> C
    RT[ReceivedTraining] --> TP
    RT --> EMP
    RT --> TR[Trainer]
    US -->|history| HE[UserStoryHistoryEntry]
    T -->|history| HE2[HistoryEntry]
    R -->|history| HE2
    US -->|comments| CM[Comment]
    T -->|comments| CM
    S -->|risks| RISK[Risk]
    RISK -->|potential_assessment / residual_assessment| RA2[RiskAssessment]
    RISK -->|risk_controls| RCTL[RiskControl]
    RCTL -->|procedure| P2[Procedure]

Scope

A Scope is the top-level organisational unit. It groups related UserStory instances, Task work items, and rule categories under a named, identifiable container. Scopes can declare parent scopes, forming a directed acyclic graph (DAG) that supports inheritance — see Scope Hierarchy.

Key constraints

  • id must follow programming identifier rules: start with a letter or _letter, followed by letters, digits, or underscores.
  • User story id values must be unique within a scope.
  • Task id values must be unique within the scope's backlog.
  • description is optional free-form text; it is inherited from parent scopes when absent.
  • Category name values must be unique within rule_categories.

Task

A Task represents a concrete work item assigned to a scope's backlog. It carries a title, a full description, a priority, a kind, and a lifecycle_status. The on_hold flag marks a task as temporarily blocked without changing its lifecycle state.

The related_acs field is a frozenset[str] of acceptance criterion references (e.g. "AC1.2") that the task addresses. It merges as a union across the scope hierarchy, the same way UserStory.tags does.

Key constraints

Field Constraint
id Integer, unique within the enclosing scope's backlog
estimated_duration Required for enhancement, bug, documentation, release, deployment
active_duration / actual_duration If only one is set, the other defaults to the same value
related_acs String references to AC ids; merged as a union

Requirements

Requirements are captured as UserStory instances broken down into AcceptanceCriterion entries, each backed by ValidationEntry evidence.

UserStory

A UserStory captures a high-level requirement from the perspective of a role using the classic As a / I want / so that structure, stored as who, what, and why fields.

Stories are classified as either functional or technical via the UserStoryType enum.

The optional dod field holds a free-form definition of done. The tags field is an unordered set of short label strings (frozenset[str]) used for categorisation; tags from parent and child stories are merged as a union.

Key constraints

  • Acceptance criterion id values must be unique within a story.
  • A story is considered validated (is_validated = True) only when it is active, has at least one active acceptance criterion, and all active criteria are validated.

AcceptanceCriterion

An AcceptanceCriterion encodes the Given / When / Then structure of a testable condition.

Field Required Description
id yes Integer, unique within the enclosing story
then yes The expected outcome
given no Initial context or state
when no Trigger condition
tags no Unordered set of short label strings; merged as union
requirement_specification no ISO 29148 document type (SRS, SyRS, or StRS)
requirement_group no Requirement category within the specification

When both classification fields are provided, requirement_group must be valid for the chosen requirement_specification. Setting requirement_group without requirement_specification raises a validation error.

A criterion is validated (is_validated = True) when its validations set is non-empty and every record has passed=True.

ValidationEntry

Validation evidence is stored as a discriminated union of two frozen record types.

ManualValidation

Recorded by a human reviewer. Requires a verdict string explaining why the criterion passed or failed.

AutomatedValidation

Linked to an automated test or check. Identified by source (the file or module) and name (the test name). During merges, automated validations are deduplicated by (source, name), keeping the most recent timestamp.

The active flag

All three entity types carry an active: bool field (default True). Setting it to False performs a soft deletion — the entity is retained in the data but excluded from validation checks. An inactive UserStory implicitly deactivates all its acceptance criteria for validation purposes, regardless of their individual active values.

Rule Categories

A Scope carries a rule_categories field — a frozenset[Category] that represents the hatto (法度): the set of scoped policy rules that govern the scope.

Category

A Category groups Commandment and Suggestion instances that apply under a stated condition. Key fields:

Field Required Description
name yes Identifier-pattern string; unique within its parent's subcategories
applicability yes Free-form description of when the category's rules apply
tags no Short label strings; defaults to empty frozenset
additive no Controls suggestion merge behaviour (see Merging); defaults to False
ruleset no The category's Ruleset; defaults to empty
subcategories no Nested child categories; merged by name when scopes are merged

Categories form a tree. The effective_commandments and effective_suggestions properties return the union of a category's own rules and all its subcategories' rules, recursively.

Commandment and Suggestion

Both are frozen value objects with a single body: str field. A non-empty, non-whitespace body is required.

  • Commandment — must be honoured without exception.
  • Suggestion — should be honoured; may be waived under justified conditions.

Ruleset

A Ruleset holds two frozensets: commandments and suggestions. Both default to empty.

Roles and Workforce

A Scope carries two workforce-related fields: roles and role_assignments.

Role

A Role is a named actor within a scope. Key fields:

Field Required Description
name yes Identifier-pattern string; unique within Scope.roles
description yes Non-empty description of the role's responsibilities
description_mode no DescriptionMode; controls how the child's description is composed with the parent's on merge; defaults to OVERRIDE
parent no The role this one is derived from; defaults to None
competences no frozenset[RoleCompetence]; competence names must be unique

The flatten() method returns a copy of the role with parent=None, resolving the full chain. Scope.collapse() calls flatten() on every role automatically.

When two scopes are merged, roles sharing the same name are implicitly linked: the child scope's role receives the parent scope's role as its parent, building an explicit hierarchy across scope levels.

Competence

Competence is a pure value object identified by the (area, category, name) triple. Two instances with identical fields are considered equal.

CompetenceLevel is an ordered int enum (NONE=0, BASIC=1, MODERATE=2, PROFICIENT=3) representing proficiency. CompetenceRelevance is an ordered int enum (UNRELATED=0, RELEVANT=1, DESIRABLE=2, KEY=4) acting as a multiplier in weighted computations.

RoleCompetence binds a Competence to a role:

Field Description
competence The skill being bound
relevance CompetenceRelevance — how important this skill is for the role
proficiency_threshold CompetenceLevel — minimum required level

When parent and child scope roles are merged, RoleCompetence bindings use max-value semantics: the independently higher relevance and proficiency threshold from either side are kept.

Employee

An Employee represents a person in the system.

Field Description
id Non-empty string; unique identifier (e.g. "EMP-001")
username Non-empty login name
competences frozenset[EmployeeCompetence]; each competence at most once

EmployeeCompetence binds a Competence to the employee's actual CompetenceLevel.

RoleAssignment, TrainingNeeds, and idoneity

A RoleAssignment links an Employee to a Role and exposes two derived metrics.

training_needs() -> TrainingNeeds — computes the per-competence gap between the employee's profile and the role's requirements. For each RoleCompetence on the role:

  • Looks up the employee's current level. Missing competences default to NONE.
  • Produces a TrainingNeed with partial_distance = max(0, threshold - level) × relevance.

TrainingNeeds.total_distance sums all partial_distance values. Zero means the employee meets every threshold; higher values indicate greater training demand.

idoneity — weighted average suitability score:

idoneity = Σ(level × relevance) / Σ(relevance)

Only non-UNRELATED competences contribute. Returns 0.0 when no relevant competences are defined on the role. The maximum value equals CompetenceLevel.PROFICIENT (3.0).

Scope.role_assignments is a frozenset[RoleAssignment]. When scopes are merged it follows child-override semantics keyed by (employee.id, role.name).

Training

TrainingProgram and ReceivedTraining are standalone entities — they are never attached to a Scope collection and carry no merge semantics.

TrainingProgram

Represents an available training course.

Field Required Description
id yes Integer identifier
title yes Non-empty course title
syllabus yes Non-empty description of course content
duration yes Positive timedelta
competences no frozenset[ProgramCompetence]; each competence at most once; defaults to empty

ProgramCompetence

Binds a Competence to the CompetenceLevel at which it is taught by the program.

Field Description
competence The skill covered by the program
level CompetenceLevel — proficiency level taught

Trainer

Value object identifying who delivered a training session.

Field Required Description
name yes Non-empty name of the trainer
organization no Employing organisation; defaults to None

ReceivedTraining

Records that an employee completed a training program.

Field Required Description
program yes The TrainingProgram completed
employee yes The Employee who attended
received_on yes datetime.date of completion
location yes Non-empty location where training took place
trainer yes The Trainer who delivered the session
evidences no frozenset[str] of certificate URLs or proof references; defaults to empty

Procedures

A Scope carries procedures: frozenset[Procedure] — documented procedures modelled as typed flowcharts.

Procedure

Field Required Description
id yes Non-empty identifier; unique within Scope.procedures; doubles as the merge key
description yes Non-empty description of the procedure's purpose
nodes yes frozenset[ProcedureNode]; node ids must be unique within the procedure

Construction validates referential integrity: every node's linkage target (its next, a decision's case targets, or a fork's branch targets) must resolve to a node id present in the same procedure. Multiple start terminators are allowed — a procedure may have more than one entry point.

ProcedureNode kinds

ProcedureNode is a discriminated union (on the kind field) of 11 flowchart node classes. Every node carries id, description, and rasci: frozenset[RasciAssignment] (role name + RASCILevel, unique role names per node).

Kind kind value Extra fields
Start terminator "start" next
End terminator "end" (none — terminal)
Activity "activity" next
Decision "decision" cases: frozenset[DecisionCase] (case label + next; unique labels)
Input "input" next
Output "output" next
Predefined procedure "predefined_procedure" procedure_id (references another Procedure.id), next
Manual operation "manual_operation" next
Manual input "manual_input" next
Fork "fork" branches: frozenset[str]
Sync "sync" next

Risks

A Scope carries risks: frozenset[Risk] — a risk register of the project's risks and how they are being controlled.

Risk

Field Required Description
id yes Integer identifier; unique within Scope.risks; doubles as the merge key
description yes Non-empty description of the risk
rasci no frozenset[RasciAssignment]; responsibility for managing the risk overall (not for executing any single control); unique role names; defaults to empty
potential_assessment yes The RiskAssessment before any controls are applied
residual_assessment yes The RiskAssessment after its controls are applied
risk_controls no frozenset[RiskControl]; defaults to empty

RiskAssessment

Scores three raw indexes, each in [0.0, 1.0], and categorizes them via a pluggable RiskAssessmentStrategy:

Field Description
impact / impact_category Consequence if the risk materialises, and its category label
probability / probability_category Likelihood of occurrence, and its category label
threat / threat_category Overall danger posed by the risk, and its category label
strategy The RiskAssessmentStrategy instance that produced the three categories
strategy_name Computed field — type(strategy).__name__ — identifying the strategy once serialized

Categories are plain str, not a fixed enum, because different strategies may use different vocabularies. The default ThresholdRiskAssessmentStrategy splits [0.0, 1.0] into four quartile buckets and labels them using the library's own 4-level, semantically ordered enums: ImpactLevel (NEGLIGIBLE < MINOR < MAJOR < SEVERE), ProbabilityLevel (RARE < UNLIKELY < LIKELY < ALMOST_CERTAIN), and RiskLevel (LOW < MODERATE < HIGH < CRITICAL). Use RiskAssessment.assess(impact, probability, threat, strategy=...) to build one from raw indexes; a custom RiskAssessmentStrategy subclass may be substituted.

RiskControl

A measure taken to reduce a risk from its potential to its residual assessment.

Field Required Description
description yes Non-empty description of the control measure
procedure no An optional, self-contained Procedure to follow; defaults to None

History and Comments

Two opt-in mixins provide cross-cutting concerns that can be applied to any entity.

Historized

Historized adds a history: frozenset[HistoryEntry] field to an entity. History entries accumulate across scope merges — no entry is ever discarded (union semantics, identical to ManualValidation).

HistoryEntry is a frozen value object with:

Field Required Description
kind yes ChangeKind — one of CREATED, UPDATED, DEACTIVATED, SUPERSEDED
timestamp yes Timezone-aware datetime of the change
author no Optional string identifying who made the change

UserStoryHistoryEntry extends HistoryEntry with two additional fields for AC-level traceability:

Field Description
added_acs frozenset[int] — AC ids added in this change event
deactivated_acs frozenset[int] — AC ids deactivated in this change event

UserStory overrides the history field type to frozenset[UserStoryHistoryEntry]. Task and Role use the base frozenset[HistoryEntry].

Commentable

Commentable adds a comments: frozenset[Comment] field to an entity. Comments accumulate across scope merges — no comment is ever discarded (union semantics).

Comment is a frozen value object with:

Field Required Description
id yes Integer identifier
body yes Non-empty comment text
timestamp yes Timezone-aware datetime of the comment
author no Optional string identifying the commenter

Which entities carry each mixin

Entity Historized Commentable
UserStory yes (with UserStoryHistoryEntry) yes
Task yes yes
Role yes no