← Library EQUILIBRIUM equilibrium-system.com

CRYSTAL OF CONSTRUCTION

CRYSTAL OF CONSTRUCTION

Below is a breakdown of construction as a system of Nodes, Rhoeber and Crystal. This is a convenient model if we want to turn the industry not just into a set of processes, but into a managed semantic, digital and organizational architecture.

Basic logic of the model

Nodes are the points of concentration of functions, resources, decisions, data and responsibility.

Ribs are connections between nodes: flows of materials, money, documents, teams, technology, people, and control.

A crystal is a complete structure in which all nodes and edges are assembled not chaotically, but according to the laws of symmetry, hierarchy, transparency and development.

In other words:

Building as a Count

The construction industry is not one chain, but a multilayer graph.

It has:

And the ribs between them form:

Nodes of construction

Nodes of design. This is the beginning of any object.

Key nodes:

Their function:

to form an answer to the question: what we build, why we build, for whom we build, on what means and in what logic of the territory.

Nodes of regulation. Without them, the system is not legitimate.

This includes:

Their function:

Ensure acceptability, security, compliance and legal framework.

Design nodes

These are birthform nodes.

This includes:

Their function:

translate the idea into an accurate system of solutions, drawings, parameters, volumes and restrictions.

Resource and supply hubs

Without them, the object will not materialize.

This includes:

Their function:

to provide the construction with everything necessary in terms of volume, time, quality and price.

Nodes of work

It is the center of physical embodiment.

This includes

Their function is to collect an object in reality.

Nodes of Finance

Construction without a financial framework is rapidly turning into chaos.

Nodes:

Their function:

ensure the stability of cash flow and predictability of execution.

Nodes of operation

In fact, the object is built not for the sake of construction, but for the sake of life after construction.

Nodes:

Their function:

Turn it into a working environment.

Nodes of the digital environment. This is the core of the future industry.

Nodes:

Their function is to make the system transparent, manageable and predictive.

Ryobra Construction

Now the main thing: nodes without edges are just dead points.

Smysnye ribs

Link:

This answers the question: why this construction has the right to be.

Legal ribs

Link:

It's the edges of legitimacy.

Information ribs

Link:

It's the edges of the data.

Financial ribs

Link:

These are the edges of value and trust.

Material ribs

Link:

These are the ribs of physical embodiment.

Management ribs

Link:

It's the ribs of coordination.

Ribs of quality. Link:

These are the ribs of reliability.

Ribs feedback

Link:

These are the ribs of evolution.

Crystal Construction

Now the main thing: what is a crystal?

A crystal is not just a network. It is a harmoniously organized multi-layered system, where each node has a place, function, degree of connection and measure of responsibility.

In construction, the crystal is:

Single spatial-digital model of the industry, where:

Construction Crystal Geometry

We will decompose the crystal into at least 7 faces.

Edge 1. Meaning

What and why is being built.

Edge 2. Space

Where, in what context.

Edge 3. Law

On what basis and by what rules.

Edge 4. Resources

From what, by whom and on what is built.

Edge 5. Production

How exactly the object is assembled in stages.

Edge 6. Finance. What money and with what sustainability.

Edge 7. Life Cycle

How the object lives after input. If you add a digital core, you get the 8 level crystal, where the digital environment connects all the other faces.

Crystal Core

Any crystal system must have a nucleus.

To build such a core should be:

Unified digital operator of construction services

It does not replace all participants, but becomes:

It turns the industry from a disparate set of participants into a single crystal of construction.

Construction Crystal Formula

Construction Crystal = Nodes × Ribs × Rules × Data × Time

Where:

Practical node architecture

Contour A. State

Nodes:

Contour B. Development and order

Nodes:

Contour C. Design

Nodes:

Contour D. Production

Nodes:

Contour E. Operation

Nodes:

Contour F. Digital platform

Nodes:

What ribs are the most critical

Strictly speaking, the industry most often collapses not because of the lack of nodes, but because of bad edges.

The most problematic ribs are usually:

That is, the main task of the reform is not just to create new nodes, but to reassemble the edges.

Crystal on the life cycle of the object

You can imagine one object as a small crystal:

Initiation. Idea, need, site, model.

Design Concept, calculations, BIM, estimate.

Harmonization. Expertise, permissions, connections.

Construction. Deliveries, works, controls, acts.

Input. Checks, documentation, launch.

Operation Monitoring, maintenance, feedback.

Transformation

Reconstruction, modernization, re-use of data. This is the crystal of the life cycle.

Speaking of Meta-Architecture

Then we can formulate it as follows:

Nodes are organs of the building organism. The ribs are its vessels, nerves, and channels. The crystal is its skeleton, shape, genome, and the law of inner harmony. Without nodes, there is no function. Without ribs, there is no coordination. Without a crystal, there is no integrity.

Management Conclusion

For the concept of a Unified Digital Operator of Construction Services, this means the following: the operator should be built not as a site, not as a set of services and not as a marketplace in a narrow sense, but as:

Crystal platform of the industry, where:

The portal should be able to show:

Final wording to be included in the concept

You can insert this:

The construction industry is considered as a crystalline network system consisting of nodes, edges and an integral digital core. The nodes represent the participants, resources, functions and stages of the object's life cycle. The ribs reflect the material, financial, informational, legal and managerial connections between them. The crystal industry is formed as a complete architecture in which all elements are interconnected, standardized, transparent and manageable in real time. On this basis, the Unified Digital Operator of Construction Services is created as a system integrator that ensures coordination, traceability and evolutionary development of the construction complex.

The strongest version of this model

If you bring the idea to the end, you can make 3 product:

This is not just a description, but a foundation for:



Table: Nodes — Ribs — Functions — Digital services

1. Outline: Design and initiation

Node

Ryobra (links)

Function

Digital services

Customer

↔ investor, ↔ designer

Task formation

Customer LC, TZ-designer

Investor

↔ bank, ↔ developer

Financing

Fin model, ROI-calculator

Land plot

↔ cadastre, ↔

Spatial base

Geoanalytics, GIS

Concept

↔ design

Object Idea

Concept Designer, AI Demand Analysis

State

↔ strategy, ↔ region

Development priorities

Register of programs, national projects

2. Outline: Regulation

Node

Ryobra

Function

Digital services

Regulatory framework

↔ project

Rules

Base of norms (GIS), auto-verification

Expertise

↔ Designer

Verification

Digital Expertise

Permissions

↔ object

Legalization

EPC (single permitting system)

Supervisory authorities

↔ building

Oversight

Online control, check-lists

Cadastre

↔ plot

Accounting

Integration with Rosreestr

3. Outline: Design

Node

Ryobra

Function

Digital services

Architect

↔ customer

Image

BIM/TIM

General Designer

↔ Contractor

Coordination

CDE (single data environment)

Engineering

↔ networks

Technical solutions

Calculation modules

Estimates

↔ Finance

Cost

Auto-estimate

BIM-model

↔ building

Digital twin

Storage BIM

4. Outline: Resources and supplies

Node

Ryobra

Function

Digital services

Suppliers

↔ building

Materials

Marketplace

Manufacturers

↔ Logistics

Production

Product catalogue

Warehouses

↔ Playground

Storage

WMS

Logistics

↔ object

Delivery

Trekking

Techniques

↔ Contractor

Mechanization

Accounting for technology

5. Outline: Construction

Node

Ryobra

Function

Digital services

General Contractor

↔ customer

Management

ERP constructions

Subcontractors

↔ General contract

Works

Register of Contractors

Playground

↔ resources

Implementation

Construction Manager

Monitoring

↔ Quality

Verification

Mobile technical supervision

Plan-schedule

↔ fact

Deadlines

Plan-fact analytics

6. Outline: Finance

Node

Ryobra

Function

Digital services

Bank

↔ project

Financing

Project financing

Treasury

↔ contracting

Payments

Treasury Module

Escrow

↔ object

Security

Escrow accounts

Contracts

↔ members

Obligations

Smart contracts

Insurance

↔ risks

Protection

Risk management

7. Outline: Operation

Node

Ryobra

Function

Digital services

UC

↔ object

Management

Operating system

User

↔ UK

Feedback

Mobile App

Resource supply

↔ object

Utilities

IoT Accounting

Monitoring

↔ system

Monitoring

Sensors, IoT

Repairs

↔ wear

Support

Repair plan

8. Outline: Digital Core (Crystal)

Node

Ryobra

Function

Digital services

Single platform

↔ all nodes

Integration

Portal

Register of objects

↔ life cycle

Accounting

Object ID

Register of participants

↔ Market

Transparency

Rating

Digital passport

↔ object

Data

Object passport

AI-analytics

↔ data

Forecast

Predictive analytics

Marketplace

↔ resources

Transactions

Trading platform

How it turns into a crystal

Now the most important thing is not just a table, but logic:

Each node — digitized → has a profile, data, history

Each rib transparently → can be seen:

  • Who is related to whom
  • under what conditions
  • with what result

The whole system is observed in real time

→ is already an operating system of construction

Key management conclusion

Now the industry:

  • There are knots
  • The ribs are chaotic.
  • Crystal is missing.

Our model does:

  • Nodes →
  • Ribs →
  • Crystal →

What it gives in practice

“Black box” → is missing, where the money, where the materials, where the delay

Corruption drops sharply → because the ribs are transparent

Accelerated construction → no breaks between nodes

→ forecasting appears system sees problems BEFORE they occurred

Next step (recommended)

Scheme (visual crystal) - how the system really looks

Platform architecture — modules, API, roles

MVP portal - at least:

  • the Register of Participants
  • Marketplace
  • Digital object passport

CRYSTAL CONSTRUCTION

(metaarchitecture of the industry)

The essence of crystal. Crystal is not a scheme. It is a living, self-consistent system where:

  • Every element is there,
  • Every connection makes sense.
  • each operation leaves a digital trace,
  • The whole system develops through feedback.

In construction, this means that the industry becomes managed as a single organism, rather than a fragmented market.

Crystal Geometry

Imagine the crystal as a polyhedron with a nucleus and layers.

NUCLEAR. Digital construction operator. Functions:

  • integration of all nodes,
  • storage of data,
  • coordination of processes,
  • analytics and forecast,
  • Transparency control.

INTERNAL LAYER (7 faces). These are the crystal structural fields:

Meaning

  • Why we build
  • Who needs it

Space

  • Where we build
  • Context of Territory

Right

  • rules and norms

Resources

  • materials, equipment, people

Production

  • Construction processes

Finance

  • money and calculations

Life Cycle

  • exploitation and development

EXTERNAL SLAY (Wednesday)

  • State
  • Market
  • Society
  • Ecology
  • Technology

This is what the crystal is constantly interacting with.

Crystal lattice (key idea)

The crystal is not held by knots, but by a grid of connections.

The main types of ribs:

  • > Information
  • > financial
  • > material
  • > legal
  • > management
  • . feedback

If at least one type of connection is weak, the crystal begins to break down.

Crystal axis (control)

The crystal has 3 main axes:

The Time Axis

Idea → project → construction → operation → transformation

Here's the data

Each action is recorded and available

RESPONSIBILITY

Every decision has an author. . . . These are the three pillars of transparency.

Fractal structure

The most powerful: each object is a small crystal

And it completely repeats the structure of the entire industry. That is:

  • house = crystal
  • Area = crystal
  • City = Crystal
  • Country = Crystal

This gives scalability without loss of controllability.

Digital double crystal. In the platform, it looks like this: Each object has:

  • ID
  • digital passport
  • BIM-model
  • history of decisions
  • financial footprint
  • status in real time

This transforms the construction from a process into an observable system.

The behavior of the crystal. Crystal should be able to:

1. Self-diagnosis

  • Where are the delays
  • Where are the risks
  • Where are the gaps

2. Self-adaptation

  • redeployment of resources
  • change of schedules

3. Self-learning

  • Analysis of past projects
  • Improving the future

The main defects of the current industry

There is no crystal, there is chaos.

  • Nodes are not related
  • Data is scattered
  • Solutions are non-transparent
  • Responsibility is blurred
  • money is lost
  • The “Floating” Period

This is not a problem of people - it is a problem of lack of structure.

What does the crystal

Connects everything into a system

  • Project ↔
  • money ↔ works
  • standards ↔ facts
  • operation ↔ design

Makes the industry manageable

You see:

  • Where is the problem
  • Who is responsible
  • What to do

. . . makes a prediction. The system begins to say, “This object will collapse in 3 months.”

Platform architecture (crystal skeleton)

Module 1. Roster

  • objects
  • Participants
  • resources

Module 2. Marketplace

  • materials
  • Contractors
  • Technics

Module 3. Project Management

  • Timeline
  • Tasks
  • control

Module 4. Finance

  • payments
  • estimates
  • control

Module 5. BIM/Digital twin

  • Object Model

Module 6. Analytics and AI

  • forecast
  • Risks
  • optimization

Crystal Formula

Can be fixed as a basic principle: Construction Crystal (Nodes × Ribs) + Data + Time + Responsibility

The most important thing (without illusions)

If you really want to change the industry,

Don't make another portal

Don’t just do the marketplace.

? do the construction operating system. Here is the wording of the strategy level:

Crystal Construction is an integrated digital and organizational architecture of the industry, based on the structuring of nodes, standardization of connections and the formation of a single digital core. Such a model ensures transparency, manageability and evolutionary development of the construction complex at all stages of the life cycle of objects.



> VISUAL SCHEME

CRYSTAL OF CONSTRUCTION

Central structure (core)

In the center of the crystal:

A SINGLE DIGITAL OPERATOR

He is not a market participant, but a coordination environment. Inside the core:

  • the Register of Objects
  • the Register of Participants
  • digital passports
  • BIM/doubles
  • AI-analytics

This is the “brain” of the system.

First circle - 7 faces of the crystal

Around the core are 7 key modules (like petals/edges):

1. MEANING

  • requirements
  • strategy
  • Development scenarios

2. SPACE

  • Land
  • Geoanalyst
  • master plan

3. LAW

  • Standards
  • permissions
  • Expertise

4. RESOURCES

  • materials
  • Technics
  • Personnel

5. PRODUCTION

  • construction
  • Contractors
  • processes

6. FINANCE

  • investments
  • payments
  • control

7. LIFE CYCLE

  • Operation
  • Service
  • feedback

Second round - knots

Each facet is broken into nodes: Example:

The term “production”

  • General Contractor
  • Subcontractors
  • Playground
  • control
  • schedule

The term “Finance”

  • Bank
  • Treasury Department
  • contracts
  • Insurance

Visually, it is a cluster within each facet

Ryobra (most important)

Now the key part of the scheme:

> ALL Knots ARE BETWEEN YOU

Types of lines (can be visually distinguished):

  • solid → material flows
  • dotted → data
  • double → finance
  • red → risks
  • green → feedback

This turns the schema into a living network, not a hierarchy.

Crystal Axes 

3 axes pass through the center:

Here is the time Idea → Project → Construction → Operation

Here's the data. All nodes are connected to the digital core

Responsibility Axis Each node has an owner

Outer shell

Outside the crystal - context:

  • State
  • Market
  • Society
  • Ecology
  • Technology

This can be depicted as a sphere around a crystal.

What it looks like (in words)

Imagine:

  • in the center - the luminous core
  • around symmetric segments
  • Inside the segments - nodes
  • There is a close network of connections between them.
  • All this pulsates data

This is no longer a scheme - it is an operating model of the industry

Simplified scheme (for document)

You can insert this:

                [EXPLATMENT]

                      ▲

                      |

[RESOURCES] > [NUCLEAR / DIGITAL OPERATOR] > [FINANCE]

      ▲                ▲                 ▲

      |                |                 |

[PRODUCTION] [DATA / AI] [PRODUCTION]

      ▲                                  ▲

      |                                  |

      ? [RIGHT] ?


How to use this scheme

For the state - the model of industry management

For investors - transparency of the system

For IT - platform architecture

For presentations - a strong visual image

INTERACTIVE CRYSTAL MAP

(platform interface)

Main screen (Core View). This is not a list or a table. It's a living crystal map.

On screen:

  • center → core (operator)
  • around → 7 faces
  • inside → knots
  • between → edges (links)

The user sees the entire system at once.

Display modes (key chip)

The same crystal switches:

 Mode 1 — Object

Shows:

  • One construction project
  • All the connections around him.

Mode 2 — Territory

  • City / Region
  • all constructions

Mode 3 — Industry

  • The entire system of the country

Interaction (UX)

Node click:

  • Who is this?
  • rating
  • projects
  • Communications

Rib click:

  • Contract
  • amount
  • Timeline
  • status

Click on the object:

  • BIM
  • Finance
  • schedule
  • Risks

Color Logic

To the interface “speak”:

  • . . . standard
  • > risk
  • The problem
  • > data
  • * Finances

Panel on the right (Control Panel)

  • filters (region, object type)
  • Mode Switching
  • Analytics
  • Warnings

Bottom panel (Timeline)

Slider time: ? can "scroll construction"

  • How it was
  • as is
  • forecast

The main value of the interface

It's not UI.

This is: - real-time industry operational map

TECHNICAL ARCHITECTURE (TC)

General architecture

Type: Microservices + Data Platform + Digital Twin

Main modules

Core Platform

  • Authorization
  • Roles
  • API-gateway

Registries

Register of objects

  • ID
  • status
  • Geo
  • Stage

Register of participants

  • Company
  • Ratings
  • History

 Digital Twin (BIM)

  • Storage of models
  • Version
  • Link to Stages

Graph Engine (KEY MODULE)

This is the heart of the system. Stores:

  • Nodes
  • Ribs
  • Communications

> Technologies:

  • Neo4j / TigerGraph

Project Management

  • Tasks
  • Timeline
  • Dependency

Financial module

  • contracts
  • payments
  • control

Marketplace

  • materials
  • Contractors
  • Technics

AI / Analytics

  • forecast of terms
  • Risk identification
  • optimization

Monitoring (IoT)

  • Sensors
  • construction
  • Operation

Data types

Nodes:

  • object
  • Participant
  • resource
  • Document

Ryobra:

  • Contract
  • delivery
  • Task
  • payment

API (example)

Get object graph

GET /api/object/{id}/graph

Get member connections: GET /api/company/{id}/relations

Get risks: /api/object/GET {id}/risks

Roles of Users

  • State
  • Investor
  • Customer
  • Contractor
  • control
  • citizen

Data Flows

1. BIM → Graph

2. Finance → Graph

3. IoT → Monitoring

4. User → UI > Everything fits in Graph Engine

? 2.7. Without what the system will not work

If there is no Graph Engine, there is no crystal

If there is no object ID - chaos

If there is no digital passport, there is no transparency.


KEY IDEA 

We are not currently building:

  • Site
  • Marketplace
  • CRM

You are building:

Graph-based Operating System for Construction


MVP CONSTRUCTION CRYSTAL

(first working version of the system)

Goal MVP . Do not try to build the entire crystal at once. Goal MVP: ? to show that the system “sees construction as a graph”. That is, MVP should be able to:

  • create an object
  • Link to participants
  • show links
  • display status
  • Identifying the problem

If not, everything else is meaningless.

What is included in MVP (minimum)

Register of objects

  • Object ID
  • Address / Geo
  • stage (project / construction / operation)
  • basic parameters

Register of participants

  • Company
  • roles (customer, contractor, etc.)
  • Rating (so far simple)

Graph Engine (core) > The most important. Stores:

  • Who is related to whom
  • What contracts
  • What Roles

Visual map (UI)

  • Nodes = Mugs
  • the = Line
  • Click → Information

The simplest object status

  • . . . okay
  • > risk
  • The problem


Basic analytics

  • Delays
  • Contractor Overload
  • Lack of communication

Architecture MVP

Simplified:

[Frontend]

     ↓

[API Gateway]

     ↓

-------------------------

|   Core Backend        |

|-----------------------|

| Objects Service       |

| Companies Service     |

| Graph Service         |

| Auth Service          |

-------------------------

     ↓

[Databases]

- PostgreSQL

- Graph DB (Neo4j)

Technology stack (optimal)

Backend - Node.js (NestJS) or Python (FastAPI). Why:

  • Quickly
  • flexible
  • easy to scale

Frontend React + Next.js. Add:

  • fast development
  • SSR
  • Convenient UI

Graph visualization (Key)

? Cytoscape.js Or ? D3.js. If you want an easier start: > React Flow

Graph Database > Neo4j. It's the heart of the crystal.

Main DB > PostgreSQL

API

  • REST (MVP is enough)
  • later → GraphQL

Hosting > Quick Start:

  • Vercel (frontend)
  • Railway / Render (backend)
  • Neo4j Aura (graph)

Authorization: Auth0 / Firebase Auth or simple JWT at launch

Data Model (Simplified)

Nodes:

Object

  • id
  • name
  • status
  • location

Company

  • id
  • name
  • role

Ribs: RELATION

  • type (contract / supplier / control)
  • start_date
  • status

Example of Graph Logic

[Object]

   |

   |----(Contract)---> [Contractor]

   |

   |----(financing)--> [Bank]

   |

   |----(control)---> [Supervisor]

This is the minimum crystal.

MVP interface (what should be)

Main screen:

  • Count (center)
  • Right panel:
    • node list
    • Filter

When Clicking:

Object:

  • status
  • Participants
  • Risks

Company:

  • projects
  • Communications

Development plan (real)

Week 1–2

  • architecture
  • Base
  • API

Week 3–4

  • Graph Engine
  • Communications

Week 5–6

  • UI + visualization

Week 7–8

  • Test
  • Launch

After 2 months you already have the system

Minimum team

  • 1 backend
  • 1 frontend
  • 1 fullstack (or CTO)
  • 1 designer (optional)

Where they usually break

Error 1

CRM instead of graph

Error 2

There is no normal data model

Error 3

Overload MVP

Error 4

Trying to “goose”

First the count, then everything else. If you have:

  • there are nodes
  • There are connections
  • there is a visual

You already have a crystal.

What do you get out MVP

  • live construction map
  • Transparent Connections
  • The first level of control
  • Investor / State Demonstration

After MVP: add: finance, BIM, marketplace

If we: I will start now MVP the National Park 6–8 Weeks - we will outshine everyone who writes strategies.

Database structure

The logic is simple: PostgreSQL stores directories, object cards, users, documents, events. Neo4j keeps the graph of connections: who is connected to whom, by what type of connection, in what status.

PostgreSQL: main tables

The users table. Users of the system.

Field

Type

Purpose

id

UUID PK

ID

email

varchar unique

Username

password_hash

varchar

Password hash

full_name

varchar

Full name

phone

varchar

Phone

status

varchar

active / blocked / invited

created_at

timestamp

Creation date

updated_at

timestamp

Update Date

Table of RolesYU Roles of access.

Field

Type

id

UUID PK

code

varchar unique

name

varchar

Examples:

The user_roles table. Linking users and roles.

Field

Type

id

UUID PK

user_id

UUID FK -> users.id

role_id

UUID FK -> roles.id

created_at

timestamp

Table companies. market participants.

Field

Type

Purpose

id

UUID PK

Company ID

name

varchar

Name

short_name

varchar

Short name

inn

varchar

Taxpayer Identification Number (INN)

ogrn

varchar

Primary State Registration Number (OGRN)

company_type

varchar

customer / contractor / supplier / bank / regulator

rating

numeric(3,2)

Rating

status

varchar

active / inactive

website

varchar

Site

created_at

timestamp

Creation date

updated_at

timestamp

Update Date

Company_users table. The user’s attachment to the company.

Field

Type

id

UUID PK

company_id

UUID FK -> companies.id

user_id

UUID FK -> users.id

position

varchar

is_primary

boolean

created_at

timestamp

Table of regions. Directory of regions.

Field

Type

id

UUID PK

code

varchar unique

name

varchar

Table of locations. Addresses and coordinates.

Field

Type

id

UUID PK

region_id

UUID FK -> regions.id

address

text

lat

numeric(10,7)

lon

numeric(10,7)

cadastral_number

varchar

created_at

timestamp

Table of projects. Construction object card.

Field

Type

Purpose

id

UUID PK

Object ID

code

varchar unique

Internal number

name

varchar

Name

description

text

Description

project_type

varchar

residential / industrial / infrastructure

lifecycle_stage

varchar

concept / design / construction / operation

status

varchar

normal / risk / problem / archived

location_id

UUID FK -> locations.id

Location

customer_company_id

UUID FK -> companies.id

Customer

start_date

date

Planned start

end_date

date

Planned completion

planned_budget

numeric(18,2)

Planned budget

actual_budget

numeric(18,2)

Actual data

created_at

timestamp

Creation date

updated_at

timestamp

Update Date

Project_stages table. Project phases.

Field

Type

id

UUID PK

project_id

UUID FK -> projects.id

stage_code

varchar

stage_name

varchar

planned_start

date

planned_end

date

actual_start

date

actual_end

date

status

varchar

created_at

timestamp

Table of contracts. contracts within the system.

Field

Type

id

UUID PK

project_id

UUID FK -> projects.id

contract_number

varchar

contract_type

varchar

customer_company_id

UUID FK -> companies.id

contractor_company_id

UUID FK -> companies.id

amount

numeric(18,2)

currency

varchar

date_start

date

date_end

date

status

varchar

created_at

timestamp

updated_at

timestamp

Table of documents. Project documents.

Field

Type

id

UUID PK

project_id

UUID FK -> projects.id

company_id

UUID FK -> companies.id null

contract_id

UUID FK -> contracts.id null

doc_type

varchar

title

varchar

file_url

text

version

integer

issued_at

date

status

varchar

created_by

UUID FK -> users.id

created_at

timestamp

Risk table for the object.

Field

Type

id

UUID PK

project_id

UUID FK -> projects.id

related_company_id

UUID FK -> companies.id null

related_stage_id

UUID FK -> project_stages.id null

risk_type

varchar

severity

varchar

probability

varchar

description

text

status

varchar

detected_at

timestamp

resolved_at

timestamp null

Table of events. Tape of events.

Field

Type

id

UUID PK

project_id

UUID FK -> projects.id null

company_id

UUID FK -> companies.id null

user_id

UUID FK -> users.id null

event_type

varchar

event_payload

jsonb

created_at

timestamp

Table project_metrics. Project metrics.

Field

Type

id

UUID PK

project_id

UUID FK -> projects.id

metric_date

date

progress_percent

numeric(5,2)

cost_variance

numeric(18,2)

schedule_variance_days

integer

open_risks_count

integer

created_at

timestamp

PostgreSQL

Minimum required:

Neo4j: graph model

In the graph, we store entities as nodes and relationships as edges.

Nodes. Primary labels:

Mandatory properties of the node. For uniformity:

Types of edges Neo4j. Main relationships

Rib Properties

For example, in PARTICIPATES_IN:

In HAS_CONTRACT:

In HAS_RISK:

Example of graph

(Company: Customer) -[:PARTICIPATES_IN {role:"customer"}]-> (Project)

(Company: GeneralContractor) -[:PARTICIPATES_IN {role:"general_contractor"}]-> (Project)

(Project) -[:HAS_CONTRACT]-> (Contract)

(Company: Customer) -[:CUSTOMER_IN]-> (Contract)

(Company: GeneralContractor) -[:CONTRACTOR_IN]-> (Contract)

(Project) -[:HAS_STAGE]-> (Stage: Construction)

(Project) -[:HAS_RISK {severity:"high"}]-> (Risk)

(Project) -[:LOCATED_AT]-> (Location)


Architecture API

For MVP REST API 1 is enough. Later you can add GraphQL for complex graph screens.

General rules API

Basic prefix

/api/v1

Format

{

  "data": {},

  "meta": {},

  "error": null

}

Error

{

  "data": null,

  "meta": {},

  "error": {

    "code": "PROJECT_NOT_FOUND",

    "message": "Project not found"

  }


Authorization Authorization: Bearer <token>


Auth API. POST /auth/login. Login.

Request

{

  "email": "user@example.com",

  "password": "secret"

}

Response

{

  "data": {

    "accessToken": "jwt",

    "refreshToken": "jwt",

    "user": {

      "id": "uuid",

      "fullName": "Ivan Petrov",

      "email": "user@example.com",

      "roles": ["customer"]

    }

  },

  "meta": {},

  "error": null

}

POST /auth/refresh. Token Update.

GET /auth/me. Current User Profile.

API companies

GET /company. List of companies. Options:

POST /company. Create a company.

Request

{

  "name": "StrojGrad" LLC,

  "shortName": "StroyGrad",

  "inn": "1234567890",

  "ogrn": "1234567890123",

  "companyType": "contractor",

  "website": "https://example.com"

}

GET /companies/{id}. Company card.

PATCH /companies/{id}. Refresh the company.


GET /companies/{id}/projects. Company projects.

GET /companies/{id}/relations. Company relations in the graph.

Response

{

  "data": {

    "nodes": [

      "id": "c1", "type": "company", "name": "StroyGrad" LLC ,

      . "id": "p1", "type": "project", "name": "LK North" .

    ],

    "edges": [

      {

        "from": "c1",

        "to": "p1",

        "type": "PARTICIPATES_IN",

        "role": "contractor",

        "status": "active"

      }

    ]

  },

  "meta": {},

  "error": null

}

API projects

GET /projects. List of projects. Filters:

POST /projects. Create a project.

Request

{

  "name": "LK North",

  "description": "Multifunctional residential complex",

  "projectType": "residential",

  "lifecycleStage": "design",

  "status": "normal",

  "location": {

    "regionId": "uuid",

    "address": "St. Petersburg, ...",

    "lat": 59.93,

    "lon": 30.31,

    "cadastralNumber": "78:00:0000000:1234"

  },

  "customerCompanyId": "uuid",

  "startDate": "2026-04-01",

  "endDate": "2028-10-31",

  "plannedBudget": 2500000000

}

GET /projects/{id}Project card.

PATCH /projects/{id}. Update the project.

GET /projects/{id}/graph. Main endpoint MVP. Returns the nodes and edges of the object.

Response

{

  "data": {

    "project": {

      "id": "uuid",

      "name": "LK North",

      "status": "risk",

      "lifecycleStage": "construction"

    },

    "nodes": [

      "id": "p1", "type": "project", "name": "LK North", "status": "risk"

      "id": "c1", "type": "company", "name": "Customer Development", "status": "active" ,

      "id": "c2", "type": "company", "name": "GenContract 1", "status": "active" ,

      "id": "r1", "type": "risk", "name": "Breaking deadlines", "status": "open"

    ],

    "edges": [

      { "from": "c1", "to": "p1", "type": "PARTICIPATES_IN", "role": "customer", "status": "active" },

      { "from": "c2", "to": "p1", "type": "PARTICIPATES_IN", "role": "general_contractor", "status": "active" },

      { "from": "p1", "to": "r1", "type": "HAS_RISK", "severity": "high" }

    ]

  },

  "meta": {},

  "error": null

}

GET /projects/{id}/timeline. Timeline of changes and events.

GET /projects/{id}/metrics. Project metrics.

GET /projects/{id}/risks. List of risks.

POST /projects/{id}/risks. Create risk.

GET /projects/{id}/documents. Project documents.

POST /projects/{id}/documents. Upload the document.

GET /projects/{id}/companies. Project participants.

POST /projects/{id}/companies. Add a participant to the project.

Request

{

  "companyId": "uuid",

  "role": "supplier",

  "startDate": "2026-05-01",

  "status": "active"

}

API stages of the project

GET /projects/{id}/stages. List of stages.

POST /projects/{id}/stages. Create a stage.

PATCH /projects/{id}/stages/{stageId}. Refresh the stage.

API contracts

GET /contracts. List of contracts. Filters:

POST /contracts. Create a contract.

Request

{

  "projectId": "uuid",

  "contractNumber": "C-2026-001",

  "contractType": "general_contract",

  "customerCompanyId": "uuid",

  "contractorCompanyId": "uuid",

  "amount": 1500000000,

  "currency": "RUB",

  "dateStart": "2026-04-10",

  "dateEnd": "2027-12-20",

  "status": "active"

}

GET /contracts/{id}. Contract card.

PATCH /contracts/{id}. Update the contract.

API Risk

GET /risks. Overall list of risks. Filters:

POST /risks. Create risk.

PATCH /risks/{id}. Change the risk.

POST /risks/{id}/resolve. Close the risk.

API documents

GET /documents/{id}. Document metadata.

GET /documents/{id}/download Download file.

API Count. This is a separate service, because it is the heart of the system.

GET /graph/projects/{id}

Graph of the project.

GET /graph/companies/{id}

Company Count.

POST /graph/query

Search or analytical query to the graph. For MVP, it is better to limit template queries rather than give an arbitrary Cypher.

Request

{

  "queryType": "project_neighbors",

  "entityId": "uuid",

  "depth": 2

}

GET /graph/search. Search for entities in the graph:

API analysts

GET /analytics/dashboard

Summary indicators:

GET /analytics/projects/status-summary

Summary of project status.

GET /analytics/companies/top-contractors

Top contractors by number of projects.

GET /analytics/risks/hotspots

Where the most risk.

API events

GET /events

Event log. Filters:

Division into services. MVP requires 5 services.

auth-service

directory-service

project-service

graph-service

analytics-service

PostgreSQL and Neo4j Sync

It's a big moment. The source of truth for cards is PostgreSQL. The source of truth for connections and detours is Neo4j.

Approach. After creating/updating the entity in PostgreSQL:

Examples of events:

Minimum DTO

CreateProjectDto

{

  "name": "string",

  "description": "string",

  "projectType": "residential",

  "lifecycleStage": "design",

  "status": "normal",

  "customerCompanyId": "uuid",

  "startDate": "2026-04-01",

  "endDate": "2027-12-31",

  "plannedBudget": 1000000

}

AddCompanyToProjectDto

{

  "companyId": "uuid",

  "role": "contractor",

  "startDate": "2026-04-01",

  "endDate": null,

  "status": "active"

}

CreateRiskDto

{

  "projectId": "uuid",

  "relatedCompanyId": "uuid",

  "riskType": "deadline_delay",

  "severity": "high",

  "probability": "medium",

  "description": "Lagging at 21 day"

}

What to do in the first version

Without this, MVP will be empty:

I will.

Can be postponed

Direct recommended stack under this scheme

Backend

Frontend

Infra


Repository Structure

/crystal-construction

  /apps

    /api-gateway

    /auth-service

    /directory-service

    /project-service

    /graph-service

    /analytics-service

    /web-client

  /packages

    /shared-types

    /shared-utils

    /ui

  /infra

    /docker

    /nginx

    /db

  /docs

    openapi.yaml

    database-schema.md

    graph-model.md

The most correct order of assembly

MVP should answer 4 questions:

If API and DB give it, you already have the working core of the Building Crystal.





1. OpenAPI

Below is a shortened, but working framework specification OpenAPI 3.1 for MVP.

openapi: 3.1.0

info:

  title: Crystal Construction API

  version: 1.0.0

  description: API MVP Platform "Crystal Construction"


servers:

  - url: https://api.crystal-construction.local/api/v1


tags:

  - name: Auth

  - name: Companies

  - name: Projects

  - name: Contracts

  - name: Risks

  - name: Graph

  - name: Analytics


paths:

  /auth/login:

    post:

      tags: [Auth]

      summary: User login

      requestBody:

        required: true

        content:

          application/json:

            schema:

              $ref: '#/components/schemas/LoginRequest'

      responses:

        '200':

          Description: Successful entry

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/AuthResponse'


  /auth/me:

    get:

      tags: [Auth]

      summary: Profile of current user

      security:

        - bearerAuth: []

      responses:

        '200':

          description: User profile

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/UserProfileResponse'


  /companies:

    get:

      tags: [Companies]

      summary: List of companies

      security:

        - bearerAuth: []

      parameters:

        - in: query

          name: search

          schema: { type: string }

        - in: query

          name: companyType

          schema: { type: string }

        - in: query

          name: status

          schema: { type: string }

        - in: query

          name: page

          schema: { type: integer, minimum: 1, default: 1 }

        - in: query

          name: limit

          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }

      responses:

        '200':

          description: List of companies

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/CompanyListResponse'


    post:

      tags: [Companies]

      Summary: Creating a company

      security:

        - bearerAuth: []

      requestBody:

        required: true

        content:

          application/json:

            schema:

              $ref: '#/components/schemas/CreateCompanyRequest'

      responses:

        '201':

          description: Company created

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/CompanyResponse'


  /companies/{id}:

    get:

      tags: [Companies]

      summary: Company card

      security:

        - bearerAuth: []

      parameters:

        - $ref: '#/components/parameters/IdPath'

      responses:

        '200':

          description: Company data

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/CompanyResponse'


    patch:

      tags: [Companies]

      Summary: Renovating the company

      security:

        - bearerAuth: []

      parameters:

        - $ref: '#/components/parameters/IdPath'

      requestBody:

        required: true

        content:

          application/json:

            schema:

              $ref: '#/components/schemas/UpdateCompanyRequest'

      responses:

        '200':

          description: Company updated

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/CompanyResponse'


  /companies/{id}/relations:

    get:

      tags: [Graph]

      summary: Company relations graph

      security:

        - bearerAuth: []

      parameters:

        - $ref: '#/components/parameters/IdPath'

        - in: query

          name: depth

          schema: { type: integer, minimum: 1, maximum: 3, default: 1 }

      responses:

        '200':

          description: Company Count

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/GraphResponse'


  /projects:

    get:

      tags: [Projects]

      summary: List of projects

      security:

        - bearerAuth: []

      parameters:

        - in: query

          name: status

          schema: { type: string }

        - in: query

          name: lifecycleStage

          schema: { type: string }

        - in: query

          name: regionId

          schema: { type: string, format: uuid }

        - in: query

          name: customerCompanyId

          schema: { type: string, format: uuid }

        - in: query

          name: search

          schema: { type: string }

        - in: query

          name: page

          schema: { type: integer, default: 1 }

        - in: query

          name: limit

          schema: { type: integer, default: 20 }

      responses:

        '200':

          description: List of projects

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/ProjectListResponse'


    post:

      tags: [Projects]

      summary: Create a project

      security:

        - bearerAuth: []

      requestBody:

        required: true

        content:

          application/json:

            schema:

              $ref: '#/components/schemas/CreateProjectRequest'

      responses:

        '201':

          description: Project created

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/ProjectResponse'


  /projects/{id}:

    get:

      tags: [Projects]

      summary: Project card

      security:

        - bearerAuth: []

      parameters:

        - $ref: '#/components/parameters/IdPath'

      responses:

        '200':

          description: Project data

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/ProjectResponse'


    patch:

      tags: [Projects]

      summary: Update the project

      security:

        - bearerAuth: []

      parameters:

        - $ref: '#/components/parameters/IdPath'

      requestBody:

        required: true

        content:

          application/json:

            schema:

              $ref: '#/components/schemas/UpdateProjectRequest'

      responses:

        '200':

          description: Project updated

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/ProjectResponse'


  /projects/{id}/graph:

    get:

      tags: [Graph]

      summary: Project graph

      security:

        - bearerAuth: []

      parameters:

        - $ref: '#/components/parameters/IdPath'

        - in: query

          name: depth

          schema: { type: integer, minimum: 1, maximum: 3, default: 2 }

      responses:

        '200':

          description: Project graph

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/ProjectGraphResponse'


  /projects/{id}/companies:

    post:

      tags: [Projects]

      summary: Add a participant to the project

      security:

        - bearerAuth: []

      parameters:

        - $ref: '#/components/parameters/IdPath'

      requestBody:

        required: true

        content:

          application/json:

            schema:

              $ref: '#/components/schemas/AddCompanyToProjectRequest'

      responses:

        '201':

          description: Member added

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/OperationResponse'


  /contracts:

    get:

      tags: [Contracts]

      summary: List of contracts

      security:

        - bearerAuth: []

      parameters:

        - in: query

          name: projectId

          schema: { type: string, format: uuid }

        - in: query

          name: customerCompanyId

          schema: { type: string, format: uuid }

        - in: query

          name: contractorCompanyId

          schema: { type: string, format: uuid }

        - in: query

          name: status

          schema: { type: string }

      responses:

        '200':

          description: List of contracts

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/ContractListResponse'


    post:

      tags: [Contracts]

      Summary: Create a contract

      security:

        - bearerAuth: []

      requestBody:

        required: true

        content:

          application/json:

            schema:

              $ref: '#/components/schemas/CreateContractRequest'

      responses:

        '201':

          description: Contract created

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/ContractResponse'


  /risks:

    get:

      tags: [Risks]

      Summary: List of risks

      security:

        - bearerAuth: []

      parameters:

        - in: query

          name: projectId

          schema: { type: string, format: uuid }

        - in: query

          name: severity

          schema: { type: string }

        - in: query

          name: status

          schema: { type: string }

      responses:

        '200':

          Description: List of risks

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/RiskListResponse'


    post:

      tags: [Risks]

      Summary: Creating Risk

      security:

        - bearerAuth: []

      requestBody:

        required: true

        content:

          application/json:

            schema:

              $ref: '#/components/schemas/CreateRiskRequest'

      responses:

        '201':

          Description: Risk created

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/RiskResponse'


  /risks/{id}/resolve:

    post:

      tags: [Risks]

      Summary: Closing the Risk

      security:

        - bearerAuth: []

      parameters:

        - $ref: '#/components/parameters/IdPath'

      responses:

        '200':

          Description: Risk is closed

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/RiskResponse'


  /analytics/dashboard:

    get:

      tags: [Analytics]

      summary: Dashboard MVP

      security:

        - bearerAuth: []

      responses:

        '200':

          description: Summary analytics

          content:

            application/json:

              schema:

                $ref: '#/components/schemas/DashboardResponse'


components:

  securitySchemes:

    bearerAuth:

      type: http

      scheme: bearer

      bearerFormat: JWT


  parameters:

    IdPath:

      in: path

      name: id

      required: true

      schema:

        type: string

        format: uuid


  schemas:

    ApiError:

      type: object

      properties:

        code: { type: string }

        message: { type: string }

      required: [code, message]


    LoginRequest:

      type: object

      properties:

        email: { type: string, format: email }

        password: { type: string }

      required: [email, password]


    AuthUser:

      type: object

      properties:

        id: { type: string, format: uuid }

        fullName: { type: string }

        email: { type: string, format: email }

        roles:

          type: array

          items: { type: string }

      required: [id, fullName, email, roles]


    AuthResponse:

      type: object

      properties:

        data:

          type: object

          properties:

            accessToken: { type: string }

            refreshToken: { type: string }

            user:

              $ref: '#/components/schemas/AuthUser'

        meta: { type: object }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    UserProfileResponse:

      type: object

      properties:

        data:

          $ref: '#/components/schemas/AuthUser'

        meta: { type: object }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    Company:

      type: object

      properties:

        id: { type: string, format: uuid }

        name: { type: string }

        shortName: { type: string }

        inn: { type: string }

        ogrn: { type: string }

        companyType: { type: string }

        rating: { type: number }

        status: { type: string }

        website: { type: string, nullable: true }

      required: [id, name, companyType, status]


    CreateCompanyRequest:

      type: object

      properties:

        name: { type: string }

        shortName: { type: string }

        inn: { type: string }

        ogrn: { type: string }

        companyType: { type: string }

        website: { type: string }

      required: [name, inn, ogrn, companyType]


    UpdateCompanyRequest:

      type: object

      properties:

        name: { type: string }

        shortName: { type: string }

        companyType: { type: string }

        rating: { type: number }

        status: { type: string }

        website: { type: string }


    CompanyResponse:

      type: object

      properties:

        data:

          $ref: '#/components/schemas/Company'

        meta: { type: object }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    CompanyListResponse:

      type: object

      properties:

        data:

          type: array

          items:

            $ref: '#/components/schemas/Company'

        meta:

          type: object

          properties:

            page: { type: integer }

            limit: { type: integer }

            total: { type: integer }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    LocationInput:

      type: object

      properties:

        regionId: { type: string, format: uuid }

        address: { type: string }

        lat: { type: number }

        lon: { type: number }

        cadastralNumber: { type: string }

      required: [regionId, address]


    Project:

      type: object

      properties:

        id: { type: string, format: uuid }

        code: { type: string }

        name: { type: string }

        description: { type: string, nullable: true }

        projectType: { type: string }

        lifecycleStage: { type: string }

        status: { type: string }

        customerCompanyId: { type: string, format: uuid }

        startDate: { type: string, format: date, nullable: true }

        endDate: { type: string, format: date, nullable: true }

        plannedBudget: { type: number, nullable: true }

        actualBudget: { type: number, nullable: true }

      required: [id, name, projectType, lifecycleStage, status]


    CreateProjectRequest:

      type: object

      properties:

        name: { type: string }

        description: { type: string }

        projectType: { type: string }

        lifecycleStage: { type: string }

        status: { type: string }

        location:

          $ref: '#/components/schemas/LocationInput'

        customerCompanyId: { type: string, format: uuid }

        startDate: { type: string, format: date }

        endDate: { type: string, format: date }

        plannedBudget: { type: number }

      required:

        - name

        - projectType

        - lifecycleStage

        - status

        - location

        - customerCompanyId


    UpdateProjectRequest:

      type: object

      properties:

        name: { type: string }

        description: { type: string }

        lifecycleStage: { type: string }

        status: { type: string }

        actualBudget: { type: number }

        endDate: { type: string, format: date }


    ProjectResponse:

      type: object

      properties:

        data:

          $ref: '#/components/schemas/Project'

        meta: { type: object }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    ProjectListResponse:

      type: object

      properties:

        data:

          type: array

          items:

            $ref: '#/components/schemas/Project'

        meta:

          type: object

          properties:

            page: { type: integer }

            limit: { type: integer }

            total: { type: integer }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    GraphNode:

      type: object

      properties:

        id: { type: string }

        type: { type: string }

        name: { type: string }

        status: { type: string, nullable: true }

        metadata:

          type: object

          additionalProperties: true

      required: [id, type, name]


    GraphEdge:

      type: object

      properties:

        from: { type: string }

        to: { type: string }

        type: { type: string }

        role: { type: string, nullable: true }

        status: { type: string, nullable: true }

        metadata:

          type: object

          additionalProperties: true

      required: [from, to, type]


    GraphResponse:

      type: object

      properties:

        data:

          type: object

          properties:

            nodes:

              type: array

              items: { $ref: '#/components/schemas/GraphNode' }

            edges:

              type: array

              items: { $ref: '#/components/schemas/GraphEdge' }

        meta: { type: object }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    ProjectGraphResponse:

      type: object

      properties:

        data:

          type: object

          properties:

            project:

              $ref: '#/components/schemas/Project'

            nodes:

              type: array

              items: { $ref: '#/components/schemas/GraphNode' }

            edges:

              type: array

              items: { $ref: '#/components/schemas/GraphEdge' }

        meta: { type: object }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    CreateContractRequest:

      type: object

      properties:

        projectId: { type: string, format: uuid }

        contractNumber: { type: string }

        contractType: { type: string }

        customerCompanyId: { type: string, format: uuid }

        contractorCompanyId: { type: string, format: uuid }

        amount: { type: number }

        currency: { type: string }

        dateStart: { type: string, format: date }

        dateEnd: { type: string, format: date }

        status: { type: string }

      required:

        - projectId

        - contractNumber

        - contractType

        - customerCompanyId

        - contractorCompanyId

        - amount

        - currency

        - status


    Contract:

      type: object

      properties:

        id: { type: string, format: uuid }

        projectId: { type: string, format: uuid }

        contractNumber: { type: string }

        contractType: { type: string }

        customerCompanyId: { type: string, format: uuid }

        contractorCompanyId: { type: string, format: uuid }

        amount: { type: number }

        currency: { type: string }

        status: { type: string }

      required: [id, projectId, contractNumber, contractType, amount, currency, status]


    ContractResponse:

      type: object

      properties:

        data:

          $ref: '#/components/schemas/Contract'

        meta: { type: object }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    ContractListResponse:

      type: object

      properties:

        data:

          type: array

          items: { $ref: '#/components/schemas/Contract' }

        meta: { type: object }

        error:

          oneOf:

            - $ref: '#/components/schemas/ApiError'

            - { type: 'null' }


    CreateRiskRequest:

      type: object

      properties:

        projectId: { type: string, format: uuid }

        relatedCompanyId: { type: string, format: uuid, nullable: true }

        riskType: { type: string }

        severity: { type: string }

        probability: { type: string }

        description: { type: string }

      required: [projectId, riskType, severity, probability, description]


    Risk:

      type: object

      properties:

        id: { type: string, format: uuid }

        projectId: { type: string, format: uuid }

        relatedCompanyId: { type: string, format