sie-analysis 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +185 -0
- data/assets/header-vector.svg +1647 -0
- data/lib/sie_analysis/amounts.rb +51 -0
- data/lib/sie_analysis/analysis.rb +233 -0
- data/lib/sie_analysis/balance_sheets.rb +88 -0
- data/lib/sie_analysis/budgets.rb +39 -0
- data/lib/sie_analysis/dimension_statements.rb +83 -0
- data/lib/sie_analysis/groups.rb +28 -0
- data/lib/sie_analysis/income_statements.rb +109 -0
- data/lib/sie_analysis/parser.rb +251 -0
- data/lib/sie_analysis/sie_tokenizer.rb +105 -0
- data/lib/sie_analysis/version.rb +5 -0
- data/lib/sie_analysis.rb +20 -0
- metadata +71 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5b974bd28fe57c5e58938fa521b1127915fea8febdd5064dd9f8e9a1efbe21c5
|
|
4
|
+
data.tar.gz: a3ab850a3005f94f448fe53702afe6f4c182cf6e00c4b908275d54285cb94d20
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9c6d582185da464d659754781d4089e5b96f7d1691f1afb7ff76f58cceabae00d9bcf4bed6e3634a28aa0b1c3a386f7c2fe320242c807d8bcd42c72e729ce2ea
|
|
7
|
+
data.tar.gz: 83063537fa8c773ba8c449a2cc30539f0cc4856500e7707694baaac56c7265966a5a593b85fe1a87c5478ee45026cdb5bee36cb711af6f8c51abe7f5ae0c3d0d
|
data/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# SIE Analysis
|
|
4
|
+
|
|
5
|
+
Turn Swedish SIE exports into income statements, account movements, budget
|
|
6
|
+
comparisons and voucher details in Ruby.
|
|
7
|
+
|
|
8
|
+
## Get started
|
|
9
|
+
|
|
10
|
+
Requires Ruby 3.2 or newer. From a local checkout, build and install the gem:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
gem build sie-analysis.gemspec
|
|
14
|
+
gem install ./sie-analysis-0.1.1.gem
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Load an export and create an analyzer:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
require "sie_analysis"
|
|
21
|
+
|
|
22
|
+
text = File.read("export.se", encoding: "UTF-8")
|
|
23
|
+
# For a file declaring #FORMAT PC8 instead:
|
|
24
|
+
# text = File.read("export.se", encoding: "IBM437:UTF-8")
|
|
25
|
+
parsed = SieAnalysis::Parser.parse(text)
|
|
26
|
+
analysis = SieAnalysis::Analysis.new(parsed)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
All monetary results are `BigDecimal`; date boundaries and voucher dates are
|
|
30
|
+
`Date` objects. Examples below abbreviate longer results with `...`.
|
|
31
|
+
|
|
32
|
+
## Income statement and monthly trends
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
parsed = SieAnalysis::Parser.parse(File.read("spec/fixtures/sie_type2_sample.se"))
|
|
36
|
+
analysis = SieAnalysis::Analysis.new(parsed)
|
|
37
|
+
report = analysis.overview(
|
|
38
|
+
from: Date.new(2024, 1, 1), to: Date.new(2024, 3, 31)
|
|
39
|
+
)
|
|
40
|
+
report[:income_statement].slice(:revenue, :expenses, :result)
|
|
41
|
+
# => { revenue: 0.3e6, expenses: 0.21e6, result: 0.9e5 }
|
|
42
|
+
|
|
43
|
+
report[:monthly_totals].map { |month| month.slice(:month, :revenue) }
|
|
44
|
+
# => [{ month: "2024-01", revenue: 0.1e6 }, ...]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The report also includes account groups, a balance sheet, cash and bank,
|
|
48
|
+
profit margin, budget information and explanatory notes.
|
|
49
|
+
|
|
50
|
+
## Find the accounts behind a total
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
analysis.account_movements(month: "2024-01", account_number: 3010)
|
|
54
|
+
# => [{ account_number: 3010, account_name: "Forsaljning", group_code: "revenue",
|
|
55
|
+
# movement: 0.1e6, dimension_code: nil, dimension_label: nil }]
|
|
56
|
+
|
|
57
|
+
analysis.account_summary(3010, month: "2024-03")
|
|
58
|
+
# => { account_number: 3010, opening_balance: 0.22e6,
|
|
59
|
+
# movement: 0.8e5, closing_balance: 0.3e6, ... }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Omit `account_number` to list all non-zero account movements, or pass
|
|
63
|
+
`group_code: "revenue"` to select a report group.
|
|
64
|
+
|
|
65
|
+
## Break down results by project or cost center
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
parsed = SieAnalysis::Parser.parse(File.read("spec/fixtures/sie_type3_sample.se"))
|
|
69
|
+
analysis = SieAnalysis::Analysis.new(parsed)
|
|
70
|
+
report = analysis.overview(
|
|
71
|
+
from: Date.new(2024, 1, 1), to: Date.new(2024, 1, 31),
|
|
72
|
+
dimension: :project
|
|
73
|
+
)
|
|
74
|
+
report[:income_statement][:groups].map { |group| group.slice(:code, :label, :amount) }
|
|
75
|
+
# => [{ code: "ABC", label: "Projekt ABC", amount: 0.15e5 }]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use `dimension: :cost_center` for cost centers. `account_movements` accepts the
|
|
79
|
+
same dimensions, with `dimension_code: "ABC"` to select an object.
|
|
80
|
+
`analysis.available_dimensions` lists the available symbols. The export must contain the relevant object balances;
|
|
81
|
+
balance-sheet totals are omitted for dimension reports.
|
|
82
|
+
|
|
83
|
+
## Inspect the postings on an account
|
|
84
|
+
|
|
85
|
+
SIE 4 exports provide voucher details and single-day account checks:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
parsed = SieAnalysis::Parser.parse(File.read("spec/fixtures/sie_type4_sample.se"))
|
|
89
|
+
analysis = SieAnalysis::Analysis.new(parsed)
|
|
90
|
+
analysis.account_summary(3010, on: Date.new(2024, 3, 15))
|
|
91
|
+
# => { opening_balance: 0.22e6, movement: 0.5e3,
|
|
92
|
+
# closing_balance: 0.2205e6, ... }
|
|
93
|
+
|
|
94
|
+
analysis.vouchers_on_account(
|
|
95
|
+
3010,
|
|
96
|
+
from: Date.new(2024, 3, 15), to: Date.new(2024, 3, 15)
|
|
97
|
+
)
|
|
98
|
+
# => [{ voucher_series: "", voucher_number: 5, date: Date.new(2024, 3, 15),
|
|
99
|
+
# description: "March sale", movement: 0.5e3 }]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Each returned amount is the net movement on the selected account in that
|
|
103
|
+
voucher. The method returns all matching vouchers; apply sorting or pagination
|
|
104
|
+
in your script if needed.
|
|
105
|
+
|
|
106
|
+
## Compare actuals with a budget
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
parsed = SieAnalysis::Parser.parse(File.read("spec/fixtures/sie_budget_sample.se"))
|
|
110
|
+
analysis = SieAnalysis::Analysis.new(parsed)
|
|
111
|
+
report = analysis.overview(
|
|
112
|
+
from: Date.new(2026, 9, 1), to: Date.new(2026, 9, 30)
|
|
113
|
+
)
|
|
114
|
+
report[:budget][:rows].first.slice(:account_number, :budget, :actual, :variance)
|
|
115
|
+
# => { account_number: 3001, budget: 0.15416e5, actual: 0.2e5, variance: 0.4584e4 }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Budgets come from `PBUDGET` records. Variance is actual minus budget, not a
|
|
119
|
+
judgment of whether the difference is favorable. Missing budgets return
|
|
120
|
+
`status: "unavailable"`. All matching budget rows are returned, including explicitly exported zero budgets.
|
|
121
|
+
|
|
122
|
+
## Calculation behavior
|
|
123
|
+
|
|
124
|
+
- Overviews use monthly `PSALDO` changes. Date arguments select months; they do
|
|
125
|
+
not filter individual postings. If an export was already date-filtered, its
|
|
126
|
+
supplied balances are used as-is.
|
|
127
|
+
- Supply one financial year per analysis and validate the requested dates
|
|
128
|
+
against that export. Years are not fetched or merged automatically.
|
|
129
|
+
- Income groups use BAS account ranges 3000–8999, including account 8999.
|
|
130
|
+
Amounts use `BigDecimal` arithmetic and are rounded to two decimals using
|
|
131
|
+
half-up rounding. Format them for display with `amount.to_s("F")`.
|
|
132
|
+
- Reports currently label currency as SEK. Only use SEK exports.
|
|
133
|
+
- Daily checks use voucher dates and require the preceding movements needed
|
|
134
|
+
to calculate opening balances. They are not a full reconciliation of the
|
|
135
|
+
export's transactions against its balances.
|
|
136
|
+
- Parsing failures raise `SieAnalysis::ParseError`. Invalid analysis arguments
|
|
137
|
+
raise `ArgumentError`. Daily checks and voucher lists require SIE 4.
|
|
138
|
+
Parsing validates the export type, financial-year headers and recognized
|
|
139
|
+
financial rows; it is not full SIE validation.
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
From the repository directory:
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
bundle install
|
|
147
|
+
bundle exec rake # Run the complete test suite
|
|
148
|
+
bundle exec rspec spec/analysis_spec.rb
|
|
149
|
+
bundle exec irb -Ilib -rsie_analysis # Explore the library interactively
|
|
150
|
+
gem build sie-analysis.gemspec # Build the distributable gem
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Implementation lives in `lib/sie_analysis/`, examples of export data in
|
|
154
|
+
`spec/fixtures/`, and tests in `spec/`. Add a small synthetic fixture and a
|
|
155
|
+
regression test when changing parsing or calculations.
|
|
156
|
+
|
|
157
|
+
To use your checkout from another Ruby project during development:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# Gemfile in the consuming project
|
|
161
|
+
gem "sie-analysis", path: "../sie-analysis"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Run `bundle install` there after adding the dependency.
|
|
165
|
+
|
|
166
|
+
## Releases
|
|
167
|
+
|
|
168
|
+
The version lives in `lib/sie_analysis/version.rb`. Update it and run
|
|
169
|
+
`bundle install` to refresh the lockfile before committing a release.
|
|
170
|
+
Push a matching tag, for example `v0.1.0`, to publish that version to RubyGems.
|
|
171
|
+
The release workflow runs the test matrix first and rejects a tag that does
|
|
172
|
+
not match the gem version. Pushing commits or opening/updating a pull request
|
|
173
|
+
does not publish a gem.
|
|
174
|
+
|
|
175
|
+
Before the first release, configure a RubyGems
|
|
176
|
+
[pending trusted publisher](https://rubygems.org/profile/oidc/pending_trusted_publishers)
|
|
177
|
+
with these values:
|
|
178
|
+
|
|
179
|
+
- Gem: `sie-analysis`
|
|
180
|
+
- Repository owner: `standout`
|
|
181
|
+
- Repository: `sie-analysis`
|
|
182
|
+
- Workflow filename: `release.yml`
|
|
183
|
+
- Environment: `release`
|
|
184
|
+
|
|
185
|
+
Publishing uses GitHub OIDC; no RubyGems API key is stored in this repository.
|