arthurGaboriau 1 weeks ago | flag | on: Add link to relevant doc

Could you share a link to find the correct date format?

Different results are to be expected between truncate(r,a,b) and max(a,min(b,r))
The former will redistribute the truncated odds between bins in [a,b]
The latter will accumulate the odds of being lesser than a to a and those of being greater than b to b
Ex:
r = uniform(9)
r_trunc = truncate(r, 1,5) || r_minmax = max(1,min(5,r))
P(r_trunc=4) = 0.2 = P(r=4)/P(r not in [1,5]) || P(r_minmax=4) = 0.1 = P(r=4)
P(r_trunc=5) = 0.2 = P(r=1)/P(r not in [1,5]) || P(r_minmax=5) = 0.5 = P(r>=5)

Lokad has its own stochastic optimization tools, comparable to Seeker from InsideOpt. Those tools are not yet publicly documented.

Those tools are using the stochastic gradient descent (SGD), but it's only a small piece of the puzzle. There are two main challenges. First, SGD only works on continuous spaces, but the supply chain problems are invariably discrete. Second, a "naive" gradient descent fails at handling any non-convex situation. Our tools addresses both challenges.

The central insight of those tools is to put a learning process at the core of the optimization process. Lokad is not the only company doing that. AlphaFold from DeepMind is also doing that for another optimization problem, aka protein folding.

The reason why SGD is very important is that it gives a direction to the descent. Yes, the pure descent is insufficient (aka second challenge above), but ignoring altogether the most likely direction of the descent is highly inefficient. Moreover, SGD is remarkably scalable.

My former research supervisor, Mehryar Mohri, at the AT&T Labs was of the opinion that having "metaheuristic algorithms" was akin to saying you have no algorithm. Two decades later, this statement is still remarkably accurate. This class of techniques is only marginally better than random exploration.

My 2cts on the case. Hope it helps.

Hi Joshua! Thanks for the question. I'll ping an internal expert to answer your question.

It should be great to add a quick example.
show chart "Test" with
plot by [T.X] slices:slice
same(T.Y)

yimingchen 3 months | flag | on: Typo in the document of `for` loop

The comment in the code


table Ranges = with 
  [| date(2010, 01, 03) as Start, date(2010, 10, 12) as End |]
  [| date(2011, 07, 23)         , date(2012, 03, 01)        |]
  [| date(2010, 09, 27)         , date(2011, 05, 31)        |]
 
Ranges.Collision = for Start in Ranges.Start, 
                       End   in Ranges.End
 
  // (true, false, false) on the 1st iteration,
  // (false, true, false) on the 2nd,
  // (false, false, true) on the 3rd  
  Ranges.NotSameRange = (Start != Ranges.Start or End != Ranges.End)
 
  // (true, false, true)  on the 1st iteration,
  // (false, true, false) on the 2nd iteration, 
  // (true, false, true)  on the 3rd iteration
  Ranges.Intersects = max(Start, Ranges.Start) <= min(End, Ranges.End)
 
  // true  on the 1st iteration,
  // false on the 2nd iteration,
  // true  on the 3rd iteration
  return any(Ranges.NotSameRange and Ranges.Intersects)
 
show table "Ranges" with
  Ranges.Start
  Ranges.End 
  Ranges.Collision // (true, false, true)

should be


  // (false, true, true) on the 1st iteration,
  // (true, false, true) on the 2nd,
  // (true, true, false) on the 3rd  
  Ranges.NotSameRange = (Start != Ranges.Start or End != Ranges.End)

yimingchen 3 months | flag | on: Unable to compile the example

table Sizes[size] = with 
  [| as size, as Weight |]
  [| "S",     0.5       |]
  [| "M",     0.75      |]
  [| "L",     1         |]
  [| "XL",    0.75      |]
 
table Colors[color] = with 
  [| as color, as Weight |]
  [| "white",  1         |]
  [| "red",    2         |]
  [| "green",  0.5       |]
  [| "blue",   0.75      |]
  [| "black",  1         |]
 
table Catalog = with 
  [| as Size, as Color, as AltColor, as Price |]
  [| "S",     "red",    "blue",      9.99     |]
  [| "M",     "blue",   "white",     10.99    |]
  [| "XL",    "black",  "green",     15.49    |]
 
expect Catalog.size = Catalog.Size
expect Catalog.color = Catalog.Color
 
table Variant = cross(Sizes, Colors)
Variant.W = Sizes.Weight * Colors.Weight 
 
// When both dimensions are provided (by name), behaves as a normal lookup.
WhiteLarge = Variant.Weight[size: "L", color: "white"]
Catalog.Alt = Variant.W[size: Catalog.size, color: Catalog.AltColor]
 
// If a single dimension is provided by name, behavior depends on whether key
// is a scalar or not. 
 
// If scalar, the result is a vector in the other table of the cross-table: 
Colors.Small = Variant.W[size: "S"]
Sizes.Blue = Variant.W[color: "blue"]
 
// If non-scalar, the lookup will automatically look for the other 
// dimension in the key's table. 
Catalog.Alt = Variant.W[color: Catalog.AltColor] // implicit 'size: Catalog.size'
 
// Note that if no label is provided, the rightmost dimension is assumed: 
Catalog.Alt = Variant.W[Catalog.AltColor] // same as the abvoe

Error message:
Error on line 17, column 18: Catalog.Color can only be assigned a value with dimension Color.

See https://try.lokad.com/34mkamdruk1ra

yimingchen 3 months | flag | on: Another Typo

Samely, the table in the section Filtered Aggregation

Pid Sold
apple 10
orange -3
banana -2

should have been

Pid Sold
apple 3
orange -2
banana -3


table Orders = with
  [| as Pid, as OrderDate, as Quantity |]
  [| "apple",  date(2020, 4, 15), 3 |]
  [| "apple",  date(2020, 4, 16), 7 |]
  [| "orange", date(2020, 4, 16), 2 |]
 
table Products = with
  [| as Pid, as MyDefault |]
  [| "apple",  -1 |]
  [| "orange", -2 |]
  [| "banana", -3 |]

Products.Sold = sum(Orders.Quantity)
  when (Orders.OrderDate < date(2020, 4, 16))
  default Products.MyDefault
  by Orders.Pid
  at Products.Pid
 
show table "Products" with
  Products.Pid
  Products.Sold

yimingchen 3 months | flag | on: Typo in the section `Empty Group`

The following code in the section Empty Group is supposed to be

| Pid | Sold |
| :-------: | :---: |
| apple | 10 |
| orange | 2 |
| banana | -3 |

instead of

| Pid | Sold |
| :-------: | :---: |
| apple | 10 |
| orange | 2 |
| banana | -2 |


table Orders = with
  [| as Pid, as OrderDate, as Quantity |]
  [| "apple",  date(2020, 4, 15), 3 |]
  [| "apple",  date(2020, 4, 16), 7 |]
  [| "orange", date(2020, 4, 16), 2 |]
 
table Products = with
  [| as Pid, as MyDefault |]
  [| "apple",  -1 |]
  [| "orange", -2 |]
  [| "banana", -3 |]
 
Products.Sold = sum(Orders.Quantity)
  default Products.MyDefault
  by Orders.Pid
  at Products.Pid
 
show table "Products" with
  Products.Pid
  Products.Sold

marinedup 3 months | flag | on: Note on match syntax (indentation)

With the change of indentation handling, only one indentation is required (and necessary).
Code examples are right.
As of 24/09/2024, when indenting too much, we get an unclear compilation error in the IDE: "Found number/text/etc. but expected end-of-line.". In this case, reduce the number of indentations to 1 only.

nboher 4 months | flag | on: New data types limitations

A small table that contains at least one enum vector may not have more than 100 million lines.
A small table that contains at least one embedding vector may not have more than 1 million lines.

remi-quentin_92 4 months | flag | on: Question 2.C.2 correction

Ref 10095235 and linked SKUs do not exist, it should be 10095232 (2 at the end instead of 5)

remi-quentin_92 4 months | flag | on: Ranvar representation

To complete this, ranvar values are bounded to 67108800 (roughly 2^26), enabling ranvar to have a bounded storage need.
Creating a ranvar with values outside of this bound will raise error "Cannot create ranvar with domain [x .. y]".

remi-quentin_92 4 months | flag | on: Rank in reversed order

If you want to rank in decreasing order, you can either:

1 - scan with a minus sign


table T = with
[| as N |]
[| 0 |]
[| 1 |]
[| 2 |]
[| 3 |]

T.rk = rank() scan -T.N

show table "" a1b4 with
T.N
T.rk

2 - Use rankrev function (not documented but similar to rank function)


table T = with
[| as N |]
[| 0 |]
[| 1 |]
[| 2 |]
[| 3 |]
 
// equivalent to rank() scan -T.N
T.rk = rankrev() scan T.N
 
show table "" a1b4 with
T.N
T.rk

remi-quentin_92 4 months | flag | on: deleted post

If you want to rank in decreasing order, you can either:

scan with a minus sign
```envision
table T = with
[| as N |]
[| 0 |]
[| 1 |]
[| 2 |]
[| 3 |]

T.rk = rank() scan -T.N

show table "" a1b4 with
T.N
T.rk
```

or use rankrev function


table T = with
[| as N |]
[| 0 |]
[| 1 |]
[| 2 |]
[| 3 |]

// equivalent to rank() scan -T.N
T.rk = rankrev() scan T.N

show table "" a1b4 with
T.N
T.rk

The documentation is not up to date, we need to add a "," between each option (start, end, horizon...)

Thanks for pointing this out. I had the same issue and also resolved it by adding a comma at the end of lines 24 and 25 in the labeled arguments (advanced) section.

bobby_pecheur2 5 months | flag | on: Extended lines Index

Quick reminder that U.Ngoes starts at 1 and goes up to N.
U.N is in [1..N]

bobby_pecheur2 5 months | flag | on: Difference with extend.range

If you have nothing to split on (the cell is empty) then the line is dropped. If you have a content but no occurence of the separator, the line is kept (= splitted over 1 line).

Using the split option at the read stage, on lines where the splitted column is empty will drop the line altogether. This behaviour is different from the extend.split which keeps lines even when the split content is empty.

bobby_pecheur2 5 months | flag | on: Difference with extend.range

That is not true if you split at the read stage. In that case, the line is dropped if you have nothing to split on.

In multiple examples on this page (forest.regress, StyleCode, etc),
newline-separated parameters are not supported by the playground, throwing error.
Adding a ',' or a ';' (depending on what the 'one-line' grammar requires) will solve the issue.

vermorel 6 months | flag | on: How to estimate inventory value in MRO?

There are multiple valuations that can be attributed to inventory depending on the task at hand, however for a MRO, the most common valuation is the fair market value. This valuation is defined as how much it would cost to buy a comparable equipment or conversely how much the company would make if selling the equipment on a marketplace like ILS.

If you do not have access to the historical data reflecting the transactions that have been made to acquire the parts, then, the most common approach consists of going back to the suppliers asking for a quote (RFQ) for the equipment. This will give you a baseline for a new equipment. With this baseline, you can derive the value of the units you have considering how much flight hours and flight cycles they have in them (considering rotables). Similarly, ILS and other comparable marketplaces are useful to survey prices of parts.

If you have a price per unit (historical transactions), then, you can apply this per-unit price as the baseline for the valuation of the inventory. You only need to discount the flight cycles and flight hours if the unit is a rotable with codified lifespan. If the inventory is old and is intended for aging fleets, then, this price might have to be depreciated because there might never be any further demand for the inventory. Although the opposite happens once in a while: the OEM has stopped producing the part, and the price of the last units on the market skyrocket.

The MTBF doesn't tell you how much your inventory is worth. It only tells you about your expected ongoing rate of component changes.

Likewise, the lead time doesn't you the inventory value either. It only tells you how proactive you need to be to reorder "soon enough" to maintain the desired quality of service.

In general, market prices are the best indicators but getting those prices is time-consuming. So the quality of the fair market value estimate is a tradeoff between what is at stake, and the labor costs involved to refine the figures.

remi-quentin_92 6 months | flag | on: Limit of exported files

As of July 2024, the limit of exported files is 200.

marinedup 6 months | flag | on: excelFormat examples

- Regular percentage with 2 digits: excelFormatPercent = "0.00%"
- Percentage with 6 digits (for example: fillrate display): excelFormatFR = "0.000000%"
- Percentage with 6 digits and plus sign for positive values (for example: fillrate increase display): excelFormatFRIncrease = "+0.000000%;-0.000000%;0%"
- Price in dollars: excelFormatUSD = "[$$-409] #,##0.00"
- Price in dollars (accounting): excelFormatAccountingUSD = "_-\[$$-409]* # ##0.00_ ;_-\[$$-409]* -# ##0.00\ ;_-\[$$-409]* -_ "

SkwamLok 6 months | flag | on: Dcoumentation Excel Format

The full excel format documentation is available here https://ecma-international.org/publications-and-standards/standards/ecma-376/
Part 1
Section 18.8.31

I wanted to share a link to this highly customizable Beer Game simulation, made by Zensimu.
You can even simulate events such as quality issues, lead time delays, activate or deactivate collaboration/information-sharing.
It was featured in an interview made with Lokad's CEO Johannes Vermorel and Conor Doherty: https://www.lokad.com/tv/2023/10/30/supply-chain-board-games/

Thank you for explaining this very clearly. I understand we want to take all relevant raw data untouched and store it in a data lake. By keeping data storage and data processing separate, we can enable scalability and experimental optimization opportunities.

"Lokad is an analytical layer that operates on top of the client’s existing transactional systems. In other words, Lokad does not replace the ERP; it supplements it with predictive optimization capabilities that realistically cannot be implemented as part of a traditional transactional system."

I misspoke in my previous question; thank you for correcting me. I definitely need to further explore the architecture of data storage and data processing within this analytical engine. Thank you for your thorough response, Joannes.

Supervised learning is used all over the place for the main probabilistic forecasting tasks: part requests (number of units), scraps (number of units), turn-around time (number of days), etc. Unsupervised learning, or rather self-supervised learning, is used for master data improvement. For example to identifying faulty or missing compatibilities between aircraft types and PN), or to identify misclassified PN within the hierarchy, or to identify incorrect stock on hand values, etc. Then, stochastic optimization is applied on top of those probabilistic predictions to generate the supply chain decisions.

Beware, all those calculations should not be done within a data lake. The purpose of the data lake should only be to collect and serve the data "as is". It should not even attempt at adding intelligence.

The high-level process is the same for all verticals, it's the fine print that varies (which sort of forecasts? which sort of decisions? etc).

njoshuabradshaw 7 months | flag | on: deleted post

I appreciate your candid response. After exploring the antipatterns and the QSC philosophy, I've realized that some organizations are not as API-minded by design (culturally). Steering the ship towards the QSC feels like stepping on leadership toes, potentially making one a martyr for the cause. But I love this analogy and have been doing so ever since this remark. Additionally, I've observed that pride often leads people to prefer internal ideas over external suggestions. Navigating emotional intelligence, professional tact, and business interactions has taught me that leadership often takes precedence over coding numerical recipes.

Hypothetically, consider a B2B aerospace enterprise with historically intermittent and messy data. It would require extensive collaboration with functional experts to create the JPM you've described. Many leaders (nodes) downstream believe data sharing is already enough and are protective of their proprietary information, necessitating new agreements. These entities also have become adversarial or just unaware, focusing on their metrics without considering broader supply chain impacts.

For example, an MRO might prioritize production metrics. service, and profits, ignoring how this gamification affects the supply chain (upstream) and ultimately the end consumer. From your lectures and Lokad TV, I've learned that a well-built data pipeline is foundational to a successful QSC initiative. The biggest risk to the QSC is organizations unwilling to share their data, compounded by a lack of understanding of what data is necessary. Often, we are seeking out unknown data while navigating political hurdles (red tape) to access even a few fields from another node's table.

My question is:

How did you navigate democratizing and evangelizing the need for data sharing in a multi-echelon supply chain with differing philosophies? Did you accomplish this challenge through courageous leadership and democratization of the QSC?

For instance, when one MRO highlights issues while others are content, or when subcontractors and upstream vendors resist sharing lead time data due to proprietary or incentive concerns, how did you handle it? I truly think there is a cultural and fundamental problem with some of our industry's supply chain's philosophy of data sharing.

So, we can share via flat files (with red tape), but is that truly sustainable? Or should that be just enough to show proof of value? We are probably stuck not knowing what is needed to share. And so perhaps there is hope, but again, once an organization gets a whiff of what the other is up to, they may block your sharing capabilities due to poor policy and lack of understanding of the objective. "You're trying to forecast?! We already do that, trust our numbers! [Buys 1000, uses none of it for 5 years, no one held responsible, money lost]"

Thank you for your answer 7 months ago. It certainly helps!

Respectfully,

Miceli_Baptiste 8 months | flag | on: Over aggregator on vectors

Note that the interval over operates on is a value delimiter and not an index delimeter. In the example above ```envision
over Orders.OrdersDate = [-1..0]```
doesn't sum over the current and previous line but on all lines where the OrdersDate >= current OrdersDate - 1 and OrdersDate <= current OrdersDate

marinedup 8 months | flag | on: seriesLegendTooltip

Property:
seriesLegendTooltip

Description:
Sets the tooltip of the series (in the legend)

Available in (that I know of):
linechart > series
chart > block > plotxy > series
chart > block > plot > series
chart > block > scatter > series

marinedup 8 months | flag | on: deleted post

*Property:*
seriesLegendTooltip

*Description:*
Sets the tooltip of the series (in the legend)

*Available in (that I know of):*
linechart > series
chart > block > plotxy > series
chart > block > plot > series
chart > block > scatter > series

The original entry was revised and extended for publication in Foresight: The International Journal of Applied Linguistics (Q2 2024). The paper can be downloaded at the top of the page, directly under the author's name.

marinedup 9 months | flag | on: Stylecode property `booleans`

booleans specifies the boolean rendering values for the boolean pipeline.

table T = with
[| as B |]
[| true |]
[| false |]

show table "" a1h3 with
T.B
T.B as "Ja/Nein" { booleans: "Ja/Nein" }
T.B as "Oui/Non" { booleans: "Oui/Non" }
T.B as "✔️/❌" { booleans: "✔️/❌" }
T.B as "True" { booleans: "True" }
T.B as "✔️" { booleans: "✔️" }
T.B as "Tru//e/N//A" { booleans: "Tru//e/N//A" }
T.B as "True///False" { booleans: "True///False" }
T.B as "True/False/Other" { booleans: "True/False/Other" }

ArthurGau 9 months | flag | on: Hierarchical options

If you need to use slicepicker instead of slicetree and you want to introduce hierarchical choices, you have the possibility of using {sliceHierarchy: } with the options : "none" | "filter" | "ask" | "clear"

The hierarchy style code impacts the next slicer
none : for full unconstrained choices
filter : removes non existant choices based on the current constraint in the slicer
ask : automatically fills the slicer to the only possible choice (if there's only one choice), otherwise clears the slicer
clear : always clear the slicer if there are no existing choices

Hover on the stylecode in the IDE for the full explanation

marinedup 9 months | flag | on: New date fields for Files table

As of 06/02/2024:
Files.ContentChangeDate, Files.ContentChangeHour and Files.ContentChangeMinute represent the modify datetime (last date when the contents of the file were changed)
Files.AnyChangeDate, Files.AnyChangeHour and Files.AnyChangeMinute represent the change datetime (last date when anything happened to the path, including moving or copying the file)

The former fields Files.ModifiedDate, Files.ModifiedHour and Files.ModifiedMinute are equivalent to Files.AnyChangeDate, Files.AnyChangeHour and Files.AnyChangeMinute and should be decommissioned soon.

manessi 9 months | flag | on: Arbitrary number of iteration variables

You can load (and are expected to if you intend to use them) an arbitrary number of iteration variable from the iterated table (or the common table shared if you load from different tables) e.g

table T = extend.range(5)
table U[u] = by T.N mod 2
T.N2 = T.N + 2
T.N3 = T.N + 3

T.S = for tn in T.N, tn2 in T.N2, tn3 in T.N3, un in u
  ...
  return ...
remi-quentin_92 10 months | flag | on: Langserv correction

Langserv may suggest to use join(T.A, G.S) instead of join(T.A; G.S). One must use a ; and not a coma.

Miceli_Baptiste 10 months | flag | on: What happens in case of equality

Sorry my link was private, please find here the shareable one: https://try.lokad.com/s/argmax_example

Miceli_Baptiste 10 months | flag | on: Ranvar representation

Sorry my link was private, please find here the shareable one: https://try.lokad.com/s/ranvar_buckets_example

Sorry my link was private, please find here the shareable one: https://try.lokad.com/s/hidden_characters_example

Miceli_Baptiste 10 months | flag | on: Difference with extend.range

Sorry my link was private, please find here the shareable one: https://try.lokad.com/s/extend_split_example

Miceli_Baptiste 10 months | flag | on: substr(text,x (x>0))

Sorry my link was private, please find here the shareable one: https://try.lokad.com/s/substr_example

Miceli_Baptiste 10 months | flag | on: Over aggregator on vectors

My script was private, please find here the sharable link: https://try.lokad.com/s/over_vector_example

Miceli_Baptiste 10 months | flag | on: Over aggregator on vectors

Note that over can also be applied to any vector.
In this example: https://try.lokad.com/ku0dv1ujtnm66?tab=Output , we are computing all the sales done duriing a day before the ticket considered.

manessi 11 months | flag | on: JIRA Documentation

JIRA Doc : https://lokad.atlassian.net/browse/LK-7562
Careful as certain options (autocomplete...) can trigger a lot of computations and slow down the dashboard.
If you're looking at a hierarchical selection, consider https://docs.lokad.com/reference/stu/slicetree/

VBX_ 11 months | flag | on: Invalid FTP url

for sftp (at least), the host url should be ftp.lokad.com

David_BH 11 months | flag | on: deleted post

You can use https://try.lokad.com to solve this workshop !

SkwamLok 11 months | flag | on: Additional Infos on random.uniform

A scalar value within vector T.R = random.xxx(T.*) depends only on:
- the position of the function within the script (every call to a function random.xxx contributes to the seed)
- The number of the lines within table T
- if it is within a loop (each, for...), the number of the iteration or the epoch

marinedup 12 months | flag | on: URL builders to use in dashboards

As of 23/01/2024, filesUrl function is replaced by 2 functions:

/// Returns an URL for the file, identified by the hash, at the provided path in /files, and (if known at compile-time) remembers that said file is referenced by this script.
[restricted]
map fileUrl(hash: text, path: text) : text as "fileurl(txt,txt)"

/// Returns an URL for the folder at the provided path in /files, and (if known at compile-time) remembers that said folder is referenced by this script.
[restricted]
map folderUrl(path: text) : text as "folderurl(txt)"

manessi 12 months | flag | on: Column autofill

Files tied to a path schema and historized are a pain to manage because as soon as you update the path schema, every script reading the historical files before that modification as a path schema will fail (unless you specifically decided to not read such column in the path schema).

A feature is available from the data dictionnary > stats page to manually create dummy columns matching the modified path schema on past files that have been broken by the path schema modification so that they can still be read by your scripts.

manessi 12 months | flag | on: Computed columns

Path schema supports computed columns e.g

export schema '/clean/SalesOrders.ion' with
  schema Schemas.SalesOrders
  NetAmountEUR : number = forex(schema.NetAmount, schema.Currency,"EUR", schema.OrderDate)
  UnitPriceEUR : number = forex(schema.UnitPrice, schema.Currency,"EUR", schema.OrderDate)

This is a nice solution to save some file storage for redundant columns.

manessi 12 months | flag | on: Create named schema from show table

It is possible in the IDE to select a show table section, right click and use the "Create schema from show" cmdlet.

David_BH 12 months | flag | on: Link to icon list

Here is the list of available icons : https://docs.lokad.com/reference/list-of-icons/

Great talk - some notes/rough translation. The questions in the Q&A are a little difficult to hear :)
https://pastebin.com/9Vc8JGqJ

dumay 12 months | flag | on: Forex default values

If the date of your forex is in the future, by default forex() returns the rate of lastForex().
If the currency is unknown, by default forex() returns the initial price.

remi-quentin_92 12 months | flag | on: Sliced markdown

"The markdown datatype is restricted to the Scalar table." is not valid anymore. You are now able to have sliced markdown tiles. You must however use Slices table.

tikhonov 12 months | flag | on: World Container Index - 04 Jan

Containers from Asia take approximately 2-4 weeks longer and costs are more than doubling.

dumay 12 months | flag | on: List of Named Colors
MBk78 Dec 20, 2023 | flag | on: Workshop data

Great, Thanks
Everything works perfectly except the suppliers table where Suppliers primary key column miss (There is only Leadtime column).
But all columns display perfectly in the first workshop playground.

vermorel Dec 19, 2023 | flag | on: Workshop data

The playground has been fixed.

marinedup Dec 18, 2023 | flag | on: URL builders to use in dashboards

/// Returns its argument as-is. If the path is known at compile-time and points to /files, remembers that said file is referenced by this script.
[restricted]
map downloadUrl(fullpath: text) : text as "downloadurl(txt)"

/// Returns an URL for the file with the provided hash, to be downloaded as the provided name.
[restricted]
map downloadUrl(hash: text, name: text) : text as "downloadurl(txt,txt)"

/// Returns an URL for the folder at the provided path in /files, and (if known at compile-time) remembers that said folder is referenced by this script.
[restricted]
map filesUrl(path: text) : text as "filesurl(txt)"

MBk78 Dec 17, 2023 | flag | on: Workshop data

Thank you

vermorel Dec 14, 2023 | flag | on: Workshop data

Working on the issue. Sorry, for the delay. Best regards, Joannes

MBk78 Dec 14, 2023 | flag | on: Workshop data

Hello, please how to download the data ?
Even in the playground there is an error "Maximum table size is 1000000" so can't see the output.

In a recent dialogue, Conor Doherty of Lokad conversed with Joannes Vermorel and Rinat Abdullin about generative AI’s impact on supply chains. Vermorel, Lokad’s CEO, and Abdullin, a technical consultant, discussed the evolution from time series forecasting to leveraging Large Language Models (LLMs) like ChatGPT. They explored LLMs’ potential to automate tasks, enhance productivity, and assist in data analysis without displacing jobs. While Vermorel remained cautious about LLMs in planning, both acknowledged their utility in composing solutions. The interview underscored the transformative role of AI in supply chain management and the importance of integrating LLMs with specialized tools.

ttarabbia Dec 04, 2023 | flag | on: On Learning in Ill-Structured Domains

"The more ill-structured the domain, the poorer the guidance for knowledge application that ‘top-down’ structures will generally provide. That is, the way abstract concepts (theories, general principles, etc.) should be used to facilitate understanding and to dictate action in naturally occurring cases becomes increasingly indeterminate in ill-structured domains."

manessi Dec 01, 2023 | flag | on: Private/Public Key

Shamelessly copy-pasting an ELI5 from Reddit on how private/public key work. Nothing fancy once explained :)
https://www.reddit.com/r/explainlikeimfive/comments/1jvduu/eli5_how_does_publicprivate_key_encryption_work/
---
The basis is really simple:

Imagine you have a computer program that will allow you to encrypt a text file or digital document by using either one of two passwords, so that once you freely choose any one of the two passwords for encrypting, then decryption can only be performed by using the other password. That is: you can't both encrypt and decrypt by using the same password; once you use one of the two possible passwords for encryption, then you can only decrypt by using the other password you did not use for encrypting.

This is all the technical basis you need.

But why is this technical basis so useful?

If we use intelligently that previous technical stuff, then we can get a nice security system working.

This is the trick: you keep one of the two passwords as a secret password only you know and no one else knows; this is called your "private key". And you let the other password be known by everybody; this will be your "public key".

Thanks to this, you can nicely perform two interesting tasks:

a) You can RECEIVE (not send) information in a secure manner: since everybody knows your public key, then they can encrypt any information they want to send to you by using your public key. Since the information has been encrypted by using your public key, then it can only be decrypted by using your private, secret key; and since you are the only one who knows your own private key, then you are the only person who will be able to decrypt the information that was sent to you. (If you want to SEND information to other people in a secure manner, then you'll have to know those people's respective public keys, and use these public keys for encrypting).

b) you can SEND information in such manner that you can absolutely prove that information was sent by YOU: if you want to send a certain information and you encrypt it by using your PRIVATE, SECRET key, then everybody will be able to decrypt and read that information, because the information will be decryptable by your public key and everybody knows your public key. So your information is not protected against reading, but, since it is decryptable by your public key, then it is a complete proof that the information was encrypted by your private, secret key. And since you are the only person who knows your own private, secret key, then it gets perfectly proven that the information was encrypted by you and no one else. This is why encrypting by using your own private key is also known as "digitally signing" the information you send.

You will find a safety stock calculation, with varying lead times, at:
https://www.lokad.com/calculate-safety-stocks-with-sales-forecasting/

The web page includes an illustrative Excel sheet to let you reproduce the calculation.

However, the bottom line is: safety stocks are a terrible model. See:
https://www.lokad.com/tv/2019/1/9/why-safety-stock-is-unsafe/

As a statistical answer, they are completely obsolete. Probabilistic forecast must be favored instead.
https://www.lokad.com/probabilistic-forecasting-definition/

15 months of historical data is ok-ish, it's a bit tricky to assess seasonality with less than 2 years, but it can be done as well.

Hope it helps,
Joannes

Miceli_Baptiste Nov 23, 2023 | flag | on: substr(text,x (x>0))

In the case of x being positive, the function returns the input text starting at the xth character. The example provided is kind of tedious with an input text of 12 characters... In this script: https://try.lokad.com/s/substr-example?tab=Output I've added a character to the input text to remove the ambiguity.

BenoitBB Nov 23, 2023 | flag | on: Note on match syntax (indentation)

match syntax must have 2 indentations spaces .

BenoitBB Nov 23, 2023 | flag | on: deleted post

title says it all. . . .

Miceli_Baptiste Nov 21, 2023 | flag | on: exponential limits

exp(x) cannot have x exceeding 87, otherwise the script would break
Note that exp(-1000) does not break but the value isn't computed, we all know it is 0 ;)

vermorel Nov 20, 2023 | flag | on: deleted post

Unfortunately, in supply chain, things can be done "a small piece" at a time. It just doesn't work. See
https://www.lokad.com/blog/2021/9/20/incrementalism-is-the-bane-of-supply-chains/

I would have very much preferred the answer to this question to be different, to have a nice incremental path that could be made available to all employees; it would have made the early life of Lokad much easier while competing against Big Vendors.

Then, don't underestimate what a supposedly "minor" employee can do. Apathy is one of the many diseases touching large companies. When nobody cares, the one person who genuinely care ends up steering the ship. All it takes to point out the obvious as many people it takes. The flaws of the "legacy" supply chain solutions are not subtle, they are glaring.

In MRO, it boils down to: uncertainty must be embraced and quantified, varying TATs matter as much as varying consumptions, etc. See an extensive review of the challenges that need to be covered https://www.lokad.com/tv/2021/4/28/miami-a-supply-chain-persona-an-aviation-mro/

Forecasting is a mean to an end, but just a mean. Focusing on forecasting as a "stand-alone thingy" is wrong. This is the naked forecast antipattern, see https://www.lokad.com/antipattern-naked-forecasts/

For an overview on how to get a supply chain initiative organized, and launched, see https://www.lokad.com/tv/2022/7/6/getting-started-with-a-quantitative-supply-chain-initiative/

Hope it helps,

njoshuabradshaw Nov 20, 2023 | flag | on: deleted post

What are the steps to integrate probabilistic forecasting into the supply chain of an Aerospace MRO (i.e, similar to your work with Air France Industries), particularly when I'm a minor player (employee) handling it independently without any investment capital, but rather as part of my job responsibilities?

Additionally, as you have discussed in your YouTube videos and articles, is forecasting truly the answer, or should the focus be more on reengineering the supply chain or implementing other process modifications across different levels?

bperraudin Nov 17, 2023 | flag | on: List of Parameters for ActionRwD functions

I completely agree with what was said in the previous comment: stochastic optimization will definitely enable to tackle this issue (and more) the right way.
Meanwhile, here is a piece of code that you could use to tackle the order multiplier problem, following the logic I described in my previous comment:


///## ACTION REWARD FUNCTION
///### Conversion to account for customer Order Multipliers
Skus.CustomerOrderMultiplier = 2
Skus.StockOnHandInLots = floor(Skus.StockOnHand/Skus.CustomerOrderMultiplier) //could also use round(), business assumption to be taken according to the pb treated
PO.OrderQtyInLots = floor(PO.OrderQty/Skus.CustomerOrderMultiplier)
CatalogPeriods.BaselineInLots = floor(CatalogPeriods.Baseline/Skus.CustomerOrderMultiplier)

///### Action reward call

Skus.WOOUncovDemandInLots, Skus.HoldingTime = actionrwd.reward(
  TimeIndex: CatalogPeriods.N
  Baseline: CatalogPeriods.BaselineInLots
  Dispersion: Skus.Dispersion ///assume Dispersion is adapted to Order Multiplier change. This should be handled in previous script
  Alpha: 0.05
  StockOnHand: Skus.StockOnHandInLots
  ArrivalTime: dirac(PO.POTimeIndex)
  StockOnOrder: PO.OrderQtyInLots
  LeadTime: Skus.SLT
  StepOfReorder: Skus.RLT)

Skus.WOOUncovDemand = Skus.WOOUncovDemandInLots * Skus.CustomerOrderMultiplier
Skus.SellThrough = (1-cdf(Skus.WOOUncovDemand + 1)) * uniform.right(1)

Regarding the customer MOQ, provided that the customers tend to always order the MOQ or a few units above the MOQ, you could use the same logic as an approximation and treat the MOQ as an Order multiplier. I can't offer a cleaner way to cope with this unfortunately, as it would require to dedicate too much time to the problem.

s40racer Nov 17, 2023 | flag | on: List of Parameters for ActionRwD functions

Thank you.

For this upcoming stochastic optimizer, could you describe the inputs/parameters data that is needed/required for the optimization to run?

vermorel Nov 17, 2023 | flag | on: List of Parameters for ActionRwD functions

Hello! We have been developing - for the past two years - a general purpose stochastic optimizer. It has passed the prototype stage and we have a short series of client that are running their production over this new thingy. Stochastic optimization (aka an optimization under a noisy loss) is exactly what you are looking for here. This will be replacing our old-school MOQ solver as well.

We are now moving forward with the development of the clean long-term version of this stochastic optimizer, but it won't become generally available before the end of 2024 (or so). Meanwhile, we can only offer ad-hoc heuristics. Sorry for the delay, it has been a really tough nut to crack.

s40racer Nov 17, 2023 | flag | on: List of Parameters for ActionRwD functions

Thank you. Your previous answer is also relevant - it helped me understand more about the actionrwd function as a whole.

Are you able to provide coding example of how to (or how Lokad typically overcomes this limitation in real-world situations), for both the order multiplier and the MOQ? Not every product has the order multiplier or MOQ pattern or requirement.

And, similarly, if actionrwd is not the solution for this type of situation, how do you overcome this in general? Coding examples will also be very helpful here as well.

Thank you.

bperraudin Nov 17, 2023 | flag | on: List of Parameters for ActionRwD functions

Ok now I understand better, sorry about my first irrelevant answer.
Unfortunately, action reward is not designed for these use cases. It assumes under the hood that the demand follows a negative binomial distribution defined by the mean and distribution in inputs.
For order multipliers, you could work you way around this limitation by converting every input of action reward (stock available, stock on order, baseline) in number of multipliers and then multiply the outputs by the size of the multiplier. This is far from perfect as you'll have to make rounding approximations during the conversions.
For MOQ, I'd say that action reward is not built for such use cases.

s40racer Nov 16, 2023 | flag | on: List of Parameters for ActionRwD functions

Thank you. I apologize if my earlier question was not clear to begin with.

To clarify, I was asking from the perspective of the MOQ that I place on my customers, or if the customers have an MOQ or order multipliers when they purchase this item from me (due to logistics constraints or economy of scales on the transportation cost). In another words, I do not have an MOQ or order multiple to my customers, but they have instituted this requirement because of transportation efficiency and constraints.

How do I take these factors into account when generating a forecast? Currently, the forecasts are generated using the actionrwd.demand function. However there are no parameters to account for the MOQ or the order multiples, and the smoothed demand and the forecast are always way under-fitted for products with these requirements.

I hope this is clear.

bperraudin Nov 16, 2023 | flag | on: Output of actionrwd function

There are 2 outputs for the function actionrwd.rwd (see the documentation here: https://docs.lokad.com/reference/abc/actionrwd.reward/):

- Demand: it estimates the probability distribution of the demand that is not covered yet by existing stock and that happens within the coverage timespan. This distribution may have non-zero probabilities in the negative values: this simply means that even if no order is placed, the entire demand might still be covered, with stock remaining at the end of the coverage timespan.
- Holding Time: estimates, for each extra unit that could be purchased, the average time it will stay in stock before being sold.

From your description, I assume you are talking of the Demand output. To complement the explanation of the documentation, you can also see this demand output as the probability to need x additional units in stock to satisfy the future demand. If x is negative, you get the probability of reaching the end of the timespan with abs(x) units in stock without making any additional order.

I hope this helps,

bperraudin Nov 16, 2023 | flag | on: List of Parameters for ActionRwD functions

Hello,

The exhaustive list of parameters is available in the documentation page of the function actionrwd.reward, in the section function signature: https://docs.lokad.com/reference/abc/actionrwd.reward/.

However, this will not help you to integrate MOQs or Order Multipliers. These constraints cannot and should not be treated using actionrwd. Indeed, the main output of actionrwd is the probability distribution of the customer demand that is not covered yet by existing stock (on hand or on order). This has no reason to be affected by MOQ/Multiplier constraints.
However, once the demand left to satisfy is obtained through actionrwd, an economic optimization must be performed to determine the best decision to take, given that demand and other parameters/constraints. It is in this optimization that the MOQ/Mulitplier constraints must be taken into account.

If a product with a demand left to satisfy on the time period considered of 5 units has a MOQ of 50 units, the decision of whether or not you should actually purchase the MOQ completely depends on economical factors. If the product is expensive and has a low margin you might be reluctant to purchase it, if it is cheap and very profitable you might want to do it. This also possibly depends on other parameters as well like budget or storage limitation which can be integrated in an economical optimization.

I hope this helps,

bperraudin Nov 13, 2023 | flag | on: Implement Forecast at Monthly level

Hello,

You'll find below an example of code that can help you generate a daily and a monthly forecast from an existing weekly forecast. I hope this will help.

The methodology I followed fits what was described in previous comments:
1. Compute the weight of each day of the week in the whole horizon considers for each group of products.
2. Multiply this weight to an already computed weekly baseline to get a daily baseline.
3. Aggregate at month level.

This is only example to be adapted to your specific use case, here are the assumptions I made:
1. The weight might differ on the seasonality group: if not the granularity can be changed or the weight can simply be computed for the whole dataset. For categories with very few sales, this logic might overfit
2. The horizon contains only full weeks or is sufficiently big for considering that having 1 extra occurrence of a given week day is negligible.
3. The weight of the days is constant over the whole horizon. In particular we completely neglect here the impact of events like Black Friday.


///Create necessary tables
table WeekDays = extend.range(7)
WeekDays.DayNum = WeekDays.N - 1 //DayNum between 0 and 6
table GroupsWeekDays = cross(Groups,WeekDays) //Groups being an existing table with 1 line per seasonality group

Sales.DayNum = Sales.Date - monday(Sales.Date)
GroupsWeekDays.DemandQty = sum(Sales.DeliveryQty) by [Items.SeasonalityGroup,Sales.DayNum] at [Groups.SeasonalityGroup,WeekDays.DayNum]
GroupsWeekDays.WeightDay = GroupsWeekDays.DemandQty /. sum(GroupsWeekDays.DemandQty) by GroupsWeekDays.SeasonalityGroup

///Compute daily forecast
table ItemsDay = cross(Items,Day)
Day.DayNum = Day.Date - monday(Day.Date)
ItemsDay.Baseline = ItemsWeek.Baseline * single(GroupsWeekDays.WeightDay) by [Groups.SeasonalityGroup,WeekDays.DayNum] at [Items.SeasonalityGroup,Day.DayNum]
ItemsDay.DemandQty = sum(Sales.DeliveryQty)

///Compute monthly forecast
table ItemsMonth = cross(Items,Month)
ItemsMonth.DemandQty = sum(Sales.DeliveryQty)
ItemsMonth.Baseline = sum(ItemsDay.Baseline) //mind partial months when analyzing the results

s40racer Nov 10, 2023 | flag | on: Implement Forecast at Monthly level

I can also use some guidance on how to change from a weekly to a daily implementation from a coding perspective.

s40racer Nov 10, 2023 | flag | on: Implement Forecast at Monthly level

Thank you.
Would you mind elaborating a bit more on the day-of-week multiplier concept you mentioned above?

Lokad has developed its own DSL, dedicated to the predictive optimization of supply chain, namely Envision https://docs.lokad.com/

ArthurGau Nov 08, 2023 | flag | on: Lexicographic order

The order is :
0-9A-Za-z

which means to test if a string is fully numerical, you can also test if it's < "A"

vermorel Nov 05, 2023 | flag | on: Implement Forecast at Monthly level

Instead of going from weekly to monthly, I would, on the contrary, suggest to go from weekly to daily, and then from daily to monthly. Keep the weekly base structure, and to introduce day-of-week multiplier. This gives you a model at the daily level. Turn this daily model into a monthly forecasting model. Indeed, having 4 or 5 weekends has a significant impact on any given month, and usually to most effective path to capture this pattern consists of operating from the daily level.

Hope it helps,

I am not overly convinced by the idea of 'agents' when it comes to system-wide optimization. Indeed, the optimization of a supply chain must be done at the system level. What is 'best' for a node (a site, a SKU, etc) is not the what is best for the whole. The 'agent' paradigm is certainly relevant for modeling purposes, but for optimization, I am not so sure.

Concerning evolution vs revolution, see 'Incrementalism is the bane of supply chains', https://www.lokad.com/blog/2021/9/20/incrementalism-is-the-bane-of-supply-chains/

Thank you for the detailed insights!. It's always enlightening to hear from experts like Lokad. While I understand Lokad's active involvement and experimentation with LLMs, I'd like to share my perspective based on your points and my own observations.

Firstly, the limitations you mentioned regarding LLMs, especially their inability to learn post their initial training, is indeed a significant challenge. Their textual (and sometimes image) processing capabilities, though remarkable, may not suffice for the intricate nuances of supply chain transactional data. This is especially true when considering that such data comprises over 90% of pertinent supply chain information.

However, I envision generative AI working in tandem with supply chain teams, not replacing them. The role of a learning agent (at each node) could be used to assist these teams, enabling them to capture a more comprehensive representation of unconstrained demand and thereby enriching their baseline models. While the potential of AI in supply chains is vast, solely relying on this technology for business practices might be too early, given its experimental nature.

In the future, I believe the challenge won't be about replacing current practices with LLM-powered processes but merging both to create a hybrid model where technology complements human expertise. Just as eCommerce companies evolved from but differ vastly from mail-order companies of the 19th century, our future supply chain practices will likely be an evolution, not a replacement, of current methods.

Miceli_Baptiste Oct 27, 2023 | flag | on: Points limit in show scatter

Note that a show scatter will fail if you are trying to show more than 5,000 points.

Hello! While we haven't publicly communicated much on the case, Lokad has been very active on the LLM front over the last couple of months. We have also an interview with Rinat Adbullin, coming up on Lokad TV, discussing more broadly LLMs for enterprises.

LLMs are surprisingly powerful, but they have their own limitations. Future breakthrough may happen, but chances are that whatever lift some of those limitations, may be something quite unlike the LLMs we have today.

The first prime limitation is that LLMs don't learn anything after the initial training (in GPT, the 'P' stands for 'pretrained'). They just perform text completions, think of it as a 1D time-series forecast where values have been replaced by words (tokens actually, aka sub-words). There are techniques to cope - somehow - with this limitation, but none of them is even close to be as good as the original LLM.

The second prime limitation is that LLMs deal with text only (possibly images too with multi-modal variants, but images are most irrelevant to supply chain purposes). Thus, LLMs cannot directly crunch transactional data, which represents more than +90% of the relevant information for a given supply chain.

Finally, it is a mistake to look at the supply chain of the future, powered by LLMs, as an extension of the present-day practices. Just like eCommerce companies have very little in common with mail-order companies that appeared in the 19th century; the same will - most likely - be true for those future practices.

acifonelli Oct 23, 2023 | flag | on: Deterministic LeadTime

In production - last check 04 July 2023 - there are:
- 180 matches of dirac used for variable assignment, like LeadTime = dirac(<number>) (and I am constraining the search only to LeadTime variables);
- 427 matches of direct use in an actionrwd.* function.
For a *total of 607 matches*. Looking for *all* the actionrwd. *calls* we have *894 matches*. Assuming that each call is independent - i.e. is not reusing a LeadTime variable already defined - we are already at *~70% of calls to* actionrwd. *not requesting a real distribution to operate**.

Repeating the same reasoning for poisson we have:
- 0 matches for variable assignment;
- 4 matches of direct use ( 1 in Lokad Customer Demo, 1 in Public Demo and 2 in client account ).

The other distributions are not used as far as the Code Search allows me to check.

For further information on the topics covered in the video, consult Lokad's technology page
https://www.lokad.com/technology/

This is why a large software vendor cannot, by default, be deemed a "safer" option than a small vendor. In B2B software, the odds of the vendor going bankrupt are usually dwarfed by the odds of the vendor discontinuing the product. The chances that Microsoft would stop supporting core offering (ex: Excel / Word) within 2 decades are low, very low. However, the same odds cannot be applied to every single product pushed by Microsoft. Yet, when it comes to long-term support, Microsoft is one of the best vendors around (generally speaking).

marinedup Oct 10, 2023 | flag | on: Other functions generating URLs

Other useful functions generating URLs are not listed in this documentation:

sliceUrl(slice: ordinal) -> text, pure function
Produces a link to the specified slice, in the current dashboard

sliceUrl(slice: ordinal, tab: text) -> text, pure function
Produces a link to the specified slice & tab, in the current dashboard

dashUrl() -> text, pure function
Produces an url towards the current project dashboard

dashUrl(tabSearch: text) -> text, pure function
Convert a tab name into an url to be used as a link to a specific dashboard tab in current project dashboard

dashUrl(project: number, tabSearch: text) -> text, pure function
Convert a project id and a tab name into an url to be used as a link to a specific dashboard tab

dashUrl(project: number) -> text, pure function
Convert a project id into an url to be used as a link to a dashboard

currentDashUrl() -> text, pure function
Produces an url towards the current run's dashboard.