Implementation in a nutshell

Copyright © 2022 Olexandr (Alex) Troyanovs'kyy. All Rights Reserved

The project is being developed on the LAMP platform enabled by the web-hosting provider.
Currently implemented software (as of June 2022) should be called as a quasi-beta version considering the following points:
- ARMO system consists of Programme Engine (PE) only, no Recognition System (RS) is implemented so far.
- Beyond ARMO system a simple version of Registry and interface of a Participant System (PS) have been implemented for testing purpose. They may be considered as possible partial prototypes. PS communicates with PE only via https.
- A web page for Admin has also been developed (not described here).

P_Name below means an ARMO Programme with the corresponding name. That name must not contain hyphens.

Modifications/specifications of the project architecture

ARMO System repository

PE’s and PS’ files are located in the ARMOLAND folder. File S_Setup.txt contains a few lines with the pattern <parameter value>:<explanation>—see example 1.

PE functionality is supported by PropelOP.php, CloseOP.php, a couple of included auxiliary .php files, and also by some pieces of code (providing a partner’s profile registration and editing) in the ServePrtcpnt.php file invoked from dialog web page via XMLHttpRequest.

PropelOP.php (executed with parameter P_Name either from Admin web page or from CloseOP.php) opens pipeline to the corresponding Programme Calendar (PC.php), reads and processes its output, schedules task "CloseOP.php <P_Name> <Label>" in crontab (if the run is not stopped), and sends corresponding message to all Programme’s participants. Also, PropelOP.php modifies table Log_Runs when the run is started or stopped.

CloseOP.php (started by cron at scheduled time) computes ARMO results and sends them to participants. To denote OP_Closed=Yes the script creates empty file OP_Closed in the P_Name’s subfolder. When results are computed and sent to participants file OP_Closed is deleted and NextOP becomes CurrentOP unless the run is stopped.

PS interface is provided by web page dialog.html and a few .php files invoked on the server utilizing AJAX. Dialog opens with P_Name data when a user selects corresponding Programme from ARMOLAND Registry and was tested with Google Chrome (Version 97.0.4692.71) and Microsoft Edge (Version 97.0.1072.69)

ARMO Programme repository

P_Name’s repository comprises mySQL database (db for short) named P_Name and subfolder
ARMOLAND/ P_Name. That subfolder contains files P_Setup.txt, CurrentOP.txt, and PC.php. P_Setup.txt contains a few lines with the pattern <parameter value>:<explanation>—see example 2.

PC.php gets forked by PropelOP.php, reads and updates CurrentOP.txt (see example 3), and sends the following output string to php://stdout
run_status = 0 | 1 | 2 (START | CONTINUE | STOP respectively),
Label is string <YYYYMMDD> denoting label of (Current | Next | After stop run) OP,
T2_str is a string in the format “Y-m-d H:I” denoting end of (Current | Next) OP in tz0,
tz0 is the name of the Programme’s timezone from DateTimeZone::listIdentifiers().

If run_status is STOP then PC creates empty file Run_Stopped in the P_Name subfolder and PC’s output contains only <run_status>,<Label>.

T2_str denotes OP’s end time in tz0 instead of UTC as it makes calendar more convenient for both admin and participants. Also, T2_str is used as a parameter in all messages/requests/responses because it’s more comfortable than to pack-unpack T2 as binary data. Currently OP is one calendar day for any Programme. However, PC code may be modified for any other Programme's schedule without affecting PE.

OP/Run status describes how a Programme is running at a given moment of time and may be one of the following:
- CurrentOP with its end time T2_str (OP is not closed & Run is not stopped),
- NextOP with its end time T2_str (OP is closed & Run is not stopped),
- Run Stopped.
The status is reflected on the dialog page and may affect availability to post deals/balances. CloseOP.php creates file OP_Closed and then immediately calls PropelOP.php which forks PC.php right away. If file CurrentOP.txt tells to stop the run then PC.php creates file Run_Stopped within less than 1 sec after OP_Closed is created, and it seems reasonable to prevent posting pending deals between closing OP and stopping the Run. So, every time when server-side script gets request about OP/Run status the script generates response what enables dialog to properly reflect option for posting accounting data, and that makes message PENDING_DEALS surplus.

If a Programme is not running its Admin may change any of the following Programme's parameters: P_mode, UnitMO, Timezone. Changing P_mode from B to D will require creating of table Deals_Journal in the corresponding db.


Currently messages are emails from PE to participants (all data from participants are posted to PE via web page during TLS session). Subject’s pattern of those emails looks like
Four messages have been added to its original list: REGISTRATION, TERMINATION, STOP (replacing CONTINUE with NextOP.T2=0), and HISTORY (containing data about requested OP). Messages DEAL_POSTED, and BALANCE_ POSTED are modifications of their origins DEALS and BALANCES. Message PENDING_DEALS have been removed. Some emails may contain attached .csv files (viewable as spreadsheets in appropriate applications). The table below specifies every email. Only REGISTRATION and TERMINATION have no <Label> in the sublect.

Message in subject Email body
TERMINATION Participant <PPID> has left the Programme.
START Current OP ends at <$T2_str> in the Programme's timezone.
CONTINUE Next OP ends at <$T2_str> in the Programme's timezone. That OP becomes Current when RESULTS message arrives.
STOP Use Label <Label> to close run on your side (if necessary).
DEAL_POSTED PPID_self = <PPID0>, PPID_partner = <PPID1>
CR | DT <Amount> (increasing AP of self to partner | decreasing AP of partner to self)
Explanation: <string up to 255 symbols>
BALANCE_POSTED PPID_self = <PPID0>, PPID_partner = <PPID1>
AP of self to partner = <Amount> = AR of partner from self.
RESULTS See attached file describing DT/CR reductions and updated AP/AR balances of your partners. | No Results
HISTORY Variations of Email body and attachments are shown in example 5.

RESULTS’s attachment is a file named <P_Name>-<Label>-<PPID>.csv (with recipient’s PPID)--see example 4.

P_Name database tables

Database has no Deal table as all posted deal’s data are immediately stored in the Deals_Journal table (which is required only for a Programme running in D mode). Also, database has no ARMO_Journal table as data in the ARMO_results table along with OP_Label are sufficient to create a RESULTS message for a participant, and every participant may decide how to store received ARMO results in their accounting system.

Five Log_* tables have been added to maintain Programme History

Below is description of existing db tables (note that original records have been modified).
Lenghts of some fields are defined in P_Setup.txt. If PPID_length is set to 0 in P_Setup.txt then PPIDs are generated by System and all PPID-related fields have type unsigned int.

Name Type / Index Description
Table Participant
Column PPID char(PPID_length)/PK Primay Key
Column Name varchar(Name_length) Name of participant
Column Email varchar(63)/unique index For receiving messages from PE
Column PWD varchar(255) Encrypted Participant's password
Table Deals_Journal
Column OP_Label char(8) Empty for pending deal
Column PPID_self char(PPID_length)
Column PPID_partner char(PPID_length)
Column D_C char(1) D | C to denote DT | CR
Column Amount decimal(NumFld_length,2) Value decreasing AP balance of Partner |
increasing AP balance of Self
Column Explanation varchar(255) Deal's comment, Invoice or Reference number
Table Run_Ledger unique index Self_Partner Contains last updated balances, not their history
Column Obligor char(PPID_length)
Column Obligee char(PPID_length)
Column AP_balance decimal(NumFld_length,2) Payables: to be paid by Obligor to Obligee
Table Edge unique index FromTo Edges of the Graph by the end of CurrentOP
Column Mark tinyint(1) UNMARKED | NO_CYCLE | ZEROED
Column NodeFrom char(PPID_length) Obligor's PPID
Column NodeTo char(PPID_length) Obligee's PPID
Column Amount1 decimal(NumFld_length,2) Reduced value of the edge
Column Amount0 decimal(NumFld_length,2) Initial positive value of the edge
Table Cycle index FromTo Contains data about reduced cycles (if any)
Column Number int(4) Number in order of cycles' reductions
Column PPID_From char(PPID_length) Begin node of the cycle's edge
Column PPID_To char(PPID_length) End node of the cycle's edge
Column AmountReduced decimal(NumFld_length,2) For any edge in the cycle
Table ARMO_results Generated by aggregating of Cycle's data
Column PPID_From char(PPID_length)/index
Column PPID_To char(PPID_length)/index
Column AmountReduced decimal(NumFld_length,2) Aggregated value reducing AP_balance for the pair (From, To)
Column InCycles varchar(255) Cycles' numbers separated with plus sign
Table Log_Runs
Column Run_Num unsigned int(10)/PK Run number is Primary Key
Column P_mode char(1) B | D
Column UnitMO char(3) Unit of measurment of obligations during the Run
Column TZ varchar(31) Valid Timezone name
Column Start char(16) Date-Time in TZ when the Run started
Column Stop char(16) Date-Time in TZ when the Run stopped
Table Log_OPs
Column Run_Num unsigned int(10) Run number of the OP
Column OP_Label char(8)/PK Primary Key
Column N_Participants unsigned int(12) At the end of the OP
Column Total_Reduced decimal(NumFld_length,2)
Column Is_Expired char(1) Y | N
Table Log_GraphEdges Stored OPs' Graphs (history of balances)
Column OP_Label char(8)/index
Column NodeFrom char(PPID_length) Obligor
Column NodeTo char(PPID_length) Obligee
Column AmountFinal decimal(NumFld_length,2) Payable after reduction
Column AmountInit decimal(NumFld_length,2) Payable initial
Table Log_Cycles Stored Cycles
Column OP_Label char(8)/index
Column Number int(4)
Column PPID_From char(PPID_length)
Column PPID_To char(PPID_length)
Column AmountReduced decimal(NumFld_length,2)
Table Log_Results Stored ARMO_results
Column OP_Label char(8)/index
Column PPID_From char(PPID_length)
Column PPID_To char(PPID_length)
Column AmountReduced decimal(NumFld_length,2)
Column InCycles varchar(255)

Computing, storing, and sending results

When Edge table (representing Graph of obligation) is created all the edges are unmarked and have positive Amount1 = Amount0. A simple (and certainly well-known) algorithm of cycles’ reduction is based on the array of adjacent edges and described below as a function returning the number of reduced cycles.

Function ReduceCycles (conn) { // conn is connection to db P_Name
Create empty array of edges;
nCycles = 0;
while unmarked edges in the Edge table remain {
  if array is empty { put first found unmarked edge into array };
  try to find next unmarked edge which starts from the end node of the last edge in the array;
  if not found { mark last edge as NO_CYCLE; remove the edge from the array }
  else {
    add found edge to the array;
    {compare the end node of the last edge and the begin node of every previous edge in the array until either
    those nodes are equal (i.e. cycle found, so minimal value among all the cycle's edges must be memorized) or
    no more edges to compare};
    if cycle was found {
      insert corresponding record into Cycle table;
      reduce value of Edge.Amount1 field in all the cycle’s edges by the minimal value;
      mark all the cycle’s edges with Amount1=0 as ZEROED;
      remove all the cycle’s edges from the array;
} // while unmarked
return nCycles;

If nCycles>0 then ARMO_results table is populated based on records in the Cycle table. Specifically, an ARMO_results’ record contains a unique pair of nodes, AmountReduced field calculated as a sum of all Cycle.AmountReduced values for the pair in different cycles, and InCycles field containing numbers of those cycles separated with plus sign.

Then ARMO_results are used to update Run_Ledger table in the following way.
For each ARMO_results’ record R {
  find Run_Ledger’s record S: S.Obligor = R.PPID_From & S.Obligee = R.PPID_To;
  // that record was an origin of the graph's edge
  S.AP_balance=S.AP_balance - R.AmountReduced;

After that CloseOP.php sends RESULTS message to every participant (creating, attaching, and then deleting file <P_Name>-<Label>-<PPID>.csv if necessary), stores tables Edge, Cycle, and ARMO_results in the corresponding Log_ tables and cleares them for the next OP. Also CloseOP.php adds new record into Log_OPs table with correponding fields' values (Is_Expired is set to 'N') and deletes expired data as described below. The script calculates N_exp as number of records with Is_Expired='N' minus number of non-expired OPs in corresponding file P_Setup.txt. If N_exp>0 then that number of earliest not expired OPs is processed in the following way: records with corresponding values of OP_Label field get deleted from tables Log_GraphEdges, Log_Cycles, Log_Results, and--in mode D--Deals_Journal; field Is_Expired in those Log_OPs' records is set to 'Y'. The script relies on the convention that OP Labels reflect chronological order of OPs.

Short User Manual

File dialog.html located in ARMOLAND folder provides web interface to a Programme selected by a user on Dialog.html contains some data about selected Programme, six buttons (OP totals, Register, Login, Reset Page, Close Window, Update OP/Run status now), several forms (initially hidden), and some pieces of Java script. Every form is simple and comprehensible. When a form is submitted corresponding request is sent to the server and its response is processed in the web browser. OP totals is available for anybody. A visitor will be prompted to enter OP Label, and corresponding OP data (Run number, Unit, Number of participants and Total amount reduced) will be displayed.

Every participant must first Register to the Programme (and not to lose their credentials as restoring of lost PPID/Password is not implemented) with a Name recognizable by other participants. Upon successful registration a Participant should receive REGISTRATION message. Every registered participant may Login and select any option available at the moment. Logoff, Edit Profile and List of Participants are always available. Edit Profile form contains Delete button which is disabled while current Programme is running. When the Programme is stopped, a Participant can delete its profile what is confirmed with TERMINATION message sent to that Participant.

Selection of "Post Deal's data" | "Post Balances" (depending on the Programme’s mode) activates corresponding form for entering accounting data. AP balances of a Participant's partners should be posted by the end of current OP. A deal may be posted any time when Programme is running. A user should select deal's type, enter amount of units and explanation (it should not start with "Voucher#"). Type CR (reflecting purchase on credit) will increase user's payables to the selected partner. Type DT (reflecting receiving of units) will decrease user's reveivables from the selected partner (i.e. partner's payables to the user). If Participant A owes Participant B 100 units and B confirmes receiving 150 units from A then A's payables to B will be 0 and B's payables to A will be 50. Every post of Deal/Balance triggers sending email with corresponding data to PPID_self (which is currently logged in) with CC to PPID_partner entered in the form’s field.

Every new run starts with empty table Run_Ledger. For a Programme in mode D a participant may post first N run's deals describing initail APs to N corresponding partners.

OP/Run status is updated every M minutes (as defined in P_Setup.txt), and pressing "Update now" button will update status immediately. If run is stopped then option "Post Deal's data" | "Post Balances" is disabled. If OP is closed then option "Post Deal's data" will enable a user to post pending deal in mode D; option "Post Balances" will be disabled until NextOP becomes CurrentOP.

Option "Request history of reductions" prompts entering Label of the OP which data should be delivered to the user. If that OP does not exist or expired then corresponding response will be displayed on the web page. Otherwise OP's data will be emailed to the user (see example 5) and that will be reflected on the web page. Option "Vouchers" is currently disabled.

So, each participant, for every Programme it participates in, should receive the following messages:
- REGISTRATION—after registration;
- TERMINATION—only on deletion of Participant's record from Programme's db;
- RESULTS (may be “No results” for some participants)—every OP;
- DEAL_POSTED / BALANCE_POSTED—after posting accounting data on the dialog web page. CC of every messages should be also received by the partner included in the data posted. For pending deal the <Label> in DEAL_POSTED message will correspond NextOP;
- HISTORY—on request about existing non-expired OP.

Examples for P_Name = Tst

1. S_Setup.txt (does not depend on P_Name)
10:Seconds to wait after PropelOP before establishing db connection
PE for beta testing:S_comment

2. P_Setup.txt
D:P_mode=D|B - Deals|Balances
1:PPID_length, 0 means PPIDs are generated by System as unsigned int
A Programme for testing:P_Comment
2:min(s) - OP/Run status' update interval in the dialog.html
2:number of non-expired OPs
-1:D0, negative means no partners' recognition

3. CurrentOP.txt
1:must be 0|1|2 for Start|Continue|Stop
2022-06-13 18:05,

4. File below presents results computed on this example for participant B.

ARMO-results for participant B of Programme Tst on 20220613 (Unit is CAU)

All debits (DT) reducing payables:
PPID_partner,DT amount,Accrued in cycles,Updated AP

All credits (CR) reducing receivables:
PPID_partner,CR amount,Accrued in cycles,Updated AR

Total reduced AP/AR:,400 / 400

5. Example of all possible variations of HISTORY message for an existing non-expired OP.

5.1. No Participants's records in the Ledger on the OP.
Email body
Data about OP 20220604 related to Participant B (Run_Number = 3, P_mode is D, UnitMO is CAU, TimeZone is America/Edmonton). No payables/receivables for the Participant on that OP.

5.2. No results for the Participant on the OP.
Email body with one attached file
Data about OP 20220614 related to Participant B (Run_Number = 4, P_mode is D, UnitMO is CAU, TimeZone is America/Edmonton). See attached file describing subgraph of obligations for the Participant. No results for it on that OP.

,,OP's obligaion amounts

5.3. Computed results for the Participant on the OP.
Email body with three attached files
Data about OP 20220613 related to Participant B (Run_Number = 4, P_mode is D, UnitMO is CAU, TimeZone is America/Edmonton). See attached files describing obligations and computed results.

,,OP's obligaion amounts

Cycle #,PPID_From,PPID_To,Amount reduced

All debits (DT) reducing payables:
PPID_partner,DT amount,Accrued in cycles

All credits (CR) reducing receivables:
PPID_partner,CR amount,Accrued in cycles

Total reduced AP/AR:,400 / 400

Registry (simplified version of this one)

ARMOLAND Registry db currently contains one table Programme described below.

Column Type / Index Description
S_name varchar(63) System name
P_name varchar(63)/PK Programme name
UnitMO char(3) Unit's abbreviation
P_mode char(1) B | D
PC_descr varchar(63) Short description of Programme Calendar
TimeZone varchar(31) Valid timezone name
PRecog char(1) Partners' recognition: Y | N
P_comment varchar(63) Comment/Designation
Status char(1) A | R (Arranged | Requested)

This table is presented on (with hidden S_name and Status) so that every arranged Programme may be selected by clicking on the corresponding row. Button "Request new" allows a user to submit the form describing a new Programme. That request (with already registered P_name) will be received and considered by Admin of corresponding ARMO System who can setup new or modify/delete existing Programme. Any such action triggers sending corresponding message to the Registry. Currently those messages are plain text files named after the Programme and appearing in the designated directory. Message processing program (scheduled in crontab) reads every new file from the directory, updates Registry db, and then deletes that file.