marinedup 1 weeks ago | 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 2 weeks ago | 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 2 weeks ago | 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 3 weeks ago | 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 4 weeks ago | 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 7 weeks ago | 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 7 weeks ago | 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 7 weeks ago | 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 7 weeks ago | 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 7 weeks ago | 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 7 weeks ago | 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 9 weeks ago | 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_ 3 months | flag | on: Invalid FTP url

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

David_BH 3 months | flag | on: deleted post

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

SkwamLok 3 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 3 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 4 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 4 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 4 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 4 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 4 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 4 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 4 months | flag | on: World Container Index - 04 Jan

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

dumay 4 months | flag | on: List of Named Colors
MBk78 4 months | 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 5 months | flag | on: Workshop data

The playground has been fixed.

marinedup 5 months | 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 5 months | flag | on: Workshop data

Thank you

vermorel 5 months | flag | on: Workshop data

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

MBk78 5 months | 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 5 months | 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 5 months | 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 5 months | 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 5 months | flag | on: Note on match syntax (indentation)

match syntax must have 2 indentations spaces .

BenoitBB 5 months | flag | on: deleted post

title says it all. . . .

Miceli_Baptiste 5 months | 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 ;)

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,

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?

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.

Thank you.

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

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.

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.

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.

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 6 months | 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,

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 6 months | 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 6 months | 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 6 months | 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 6 months | 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 6 months | 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 6 months | 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 6 months | 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 7 months | 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.

"The returned text value that contains an URL can be rendered as a link through the StyleCode element {text: "link"}"
{text: link} is deprecated, {href: #(link)} or {href: #[T.Link]} should be used instead

marinedup 7 months | flag | on: sliceSearchUrl overloads

sliceSearchUrl(sliceSearch: text) 🡒 text, pure function
Convert an inspector name search key into an url to be used as a link to a specific slice in current project dashboard

sliceSearchUrl(project: number, sliceSearch: text) 🡒 text, pure function
Convert a project id and an inspector name search key into an url to be used as a link to a specific dashboard slice

sliceSearchUrl(sliceSearch: text, tabSearch: text) 🡒 text, pure function
Convert an inspector name search key and a tab name into an url to be used as a link to a specific dashboard slice and tab in the current project dashboard

sliceSearchUrl(project: number, sliceSearch: text, tabSearch: text) 🡒 text, pure function
Convert a project id, an inspector name search key and a tab name into an url to be used as a link to a specific dashboard slice and tab

Conor 7 months | flag | on: ABC XYZ Analysis [Pic]

For Lokad's detailed analysis of the practice, see https://www.lokad.com/abc-xyz-analysis-inventory/

vermorel 7 months | flag | on: Unicity of ranvar after transform

The function transform should be understood from the perspective of the divisibility of random variables, see https://en.wikipedia.org/wiki/Infinite_divisibility_(probability)

However, just like not all matrices can be inverted, not all random variables can be divided. Thus, Lokad adopts a pseudo-division approximate approach which is reminiscent (in spirit) to the pseudo-inverse of matrices. This technique is dependent on the chosen optimization criteria, and indeed, in this regards, although transform does return a "unique" result, alternative function implementations could be provided as well.

vermorel 7 months | flag | on: Cross Entropy Loss Understanding

Cross-entropy is merely a variant of the likelihood in probability theory. Cross-entropy works on any probability distribution as long as a density function is available. See for example https://docs.lokad.com/reference/jkl/loglikelihood.negativebinomial/

If you can produce a parametric density distribution, then, putting pathological situations aside, you can regress it through differentiable programming. See fleshed out examples at https://www.lokad.com/tv/2023/1/11/lead-time-forecasting/

In the article, it is mentioned that Lokad collected empirical data which supports the claim that Cross Entropy is usually the most efficient metric to optimize, rather than MSE, MAPE, CRPS, etc. Is it possible to view that data?

No, unfortunately for two major reasons.

First, Lokad has strict NDAs in place with all our client companies. We do not share anything, not even derivative data, without the consent of all the parties involved.

Second, this claim should be understood from the perspective the experimental optimization paradigm, which is (most likely) not what you think. See https://www.lokad.com/tv/2021/3/3/experimental-optimization/

Hope it helps,
Joannes

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

Side note on this function: if you extend.split a line not containing any of the S.Separators you will still get the line in the resulting table. This is not the same as an extend.range for instance.
Script to illustrate this case: https://try.lokad.com/s/extend.split

vermorel 7 months | flag | on: Lag Forecasting

I have a few tangential remarks, but I firmly believe this is where you should start.

First, what is the problem that you are trying to solve? Here, I see you struggling with the concept of "lag", but what you are trying to achieve in unclear. See also https://www.lokad.com/blog/2019/6/3/fall-in-love-with-the-problem-not-the-solution/

Second, put aside Excel entirely for now. It is hindering, not helping, your journey toward a proper understanding. You must be able to reason about your supply chain problem / challenge without Excel; Excel is a technicality.

Third, read your own question aloud. If you struggle to read your own prose, then probably, it needs to be rewritten. Too frequently, I realize, upon reading my own draft that the answer was in front of me once the question is properly (re)phrased.

Back to your question / statement, it seems you are confusing / conflating two distinct concepts:

  • The forecasting horizon
  • The lead times (production / dispatch / replenishment)

Then, we have also the lag which is a mathematical concept akin to time-series translation.

Any forecasting process is horizon-dependent, and no matter how you approach the accuracy, the accuracy will also be horizon dependency. The duration of between the time of cut-off and the time of the forecast is frequently referred to as the lag because in order to backtest, you will adding "lag" to your time-series.

Any supply chain decision takes time to come to pass, i.e. there is a lead time involved. Again, in order to factor those delays, it is possible to add "lag" to your time-series to reflect the various delays.

Lagging (aka time-series shift, time-series translation) is just a technicality to factor any kind of delay.

Hope it helps.

ramanathanl 7 months | flag | on: Lag Forecasting

Link for the Excel file
Copy and paste the entire link in a new window (do not click directly on the link as it does not seem to redirect correctly)

https://1drv.ms/x/s!AmdAMe2CGp70kgXFYSnCr0-SHoEV?e=prEmbD

remi-quentin_92 7 months | flag | on: Values of alpha parameter

The value of alpha in this example is very high. I would suggest to use a value close to 0.05 (depending on how much you want your sales are correlated).

ToLok 7 months | flag | on: Unicity of ranvar after transform

Formally, $P[X=n/a]$ is not a random variable but a scalar and the corresponding ranvar is not unique.
Could we add more details about how the ranvar returned by transform() is chosen ?
A graphical example might also be a nice addition.
Thanks!

A very common use case is searching for hidden characters in any string (most commonly in Product references).
This script: https://try.lokad.com/s/hiddencharacters shows how we can detect special characters in order to fix any corrupted text.

Important to note that in the upload read at the beginning of the script:

read upload "myEditable" as myEditable with ..

and where the editable is defined:

editable: "myEditable"

that this is a case sensitive feature. So if editable: "myeditable" is written (lower case `e` instead of upper case `E`) then you will not have an error message but your values will not be saved correctly when updating the table and running it from the dashboard. The two names need to be exactly aligned for each character not just the name.

Effective MRO (maintenance, repair and overhaul) requires meticulous management of up to several million parts per plane, where any unavailability can result in costly aircraft-on-ground (AOG) events. Traditional solutions to manage this complexity involve implementing safety stock formulas or maintaining excessive inventory, both of which have limitations and can be financially untenable. Lokad, through a probabilistic forecasting approach, focuses on forecasting the failure or repair needs of every individual part across the fleet and assessing the immediate and downstream financial impact of potential AOG events. This approach can even lead to seemingly counter-intuitive decisions, such as not stocking certain parts and instead paying a premium during actual need, which may, paradoxically, be more cost-effective than maintaining surplus inventory. Furthermore, Lokad’s approach automates these decision-making processes, reducing squandered time and bandwidth and increasing operational efficiency.

Miceli_Baptiste 8 months | flag | on: Ranvar representation

Ranvars have buckets that spread over multiple values.
The first such bucket is the 65th (meaning that the probability for 65 and 66 are always the same in a ranvar), so dirac(65) actually spread over two values (65 and 66).
We have again 64 buckets with 2 values each,, and then 64 buckets with four values, etc .. so the thresholds are : 64, 196, 452, … (every one being of the form $\sum_{0..n}(64*2^n)$ )

Miceli_Baptiste 8 months | flag | on: Ranvar representation

Ranvars have buckets that spread over multiple values.
The first such bucket is the 65th (meaning that the probability for 65 and 66 are always the same in a ranvar), so dirac(65) actually spread over two values (65 and 66).
We have again 64 buckets with 2 values each,, and then 64 buckets with four values, etc .. so the thresholds are : 64, 196, 452, … (every one being of the form $\sum_{0..n}(64*2^n)$ )

Example script: https://try.lokad.com/6rk5wgpaf4mp0?tab=Output

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

In case of multiple T.a values, the returned T.b value is the first value encountered.
In fact, argmax is a process function scanning the table in its default order and will return different values in case of equality for two equivalent tables ordered in a different way.

This script https://try.lokad.com/5c15t7ajn1j38?tab=Code illustrates this equality management, the usage of the function and highlights the order importance with the Hat.

A free public tutorial on how to use Envision (Lokad's DSL) to analyze retail suppliers.

Yes, just use text interpolation to insert your text values. See below:


table T = with 
  [| date(2021, 2, 1) as D |]
  [| date(2022, 3, 1) |]
  [| date(2023, 4, 1) |]

maxy = isoyear(max(T.D))

show table "My tile tile with \{maxy}" a1b3 with
  T.D as "My column header with \{maxy}"
  random.integer(10 into T) as "Random" // dummy

On the playground https://try.lokad.com/s/ad-hoc-labels-in-table-tile

Does the AI use a time series? Lol

As it is common for the buzzword of the year in supply chain - a lot of noise, but very little substance.

jamalsan 9 months | flag | on: Lag Forecasting

My two cents: in a classical setting, manufacturing would have a frozen horizon period and use the net demand + stock policy to define its procurement and production at T0. Additionally you would have sourced more raw material than your short term demand (again safety stock in its classical sense + lot quantity from the tier1 supplier).
In each cycle the base forecast is converted in net demand for the next node (your excess material / existing stock would be subtracted from the forecast)

ramanathanl 9 months | flag | on: Lag Forecasting

I have a few doubts regarding the concept of "Lags" in forecasting.
Let T0, T1, T2... be the time periods, with T0 being the current time period. "Row 2" in the attached Excel gives the forecast generated in time period T0 for the next month onwards, T1, T2...

After time period T0 gets over, and we reach time period T1, the forecast is again generated for time periods T2, T3, and so on. "Row 3" in Excel gives us this.

The "Actual" sales observed in each time period are given by "Row 8", highlighted in Green.

"Lag 1" signifies the forecast for the next immediate Time period. So forecast generated in "T0" for "T1"; forecast generated in "T1" for "T2" and so on. The same is highlighted in a shade of yellow and the successive snapshots are in "Row 10".

"Lag 2" signifies the forecast for 2 Time periods from now. So forecast generated in "T0" for "T2"... and the successive snapshots are in "Row 11" highlighted in light blue.

Likewise for "Lag 3" and "Lag 4".

Let us consider a company, and let us assume "Lag 4" is used for the procurement of Raw Materials.
"Lag 3" is used for Manufacturing.
"Lag 2" is used for dispatching to the DCs.
"Lag 1" is used for replenishing the stores.

So if we are in "T0", Lag 4 forecast = 420 units, and we will procure raw material worth this.
After 1 time period elapses, we are in "T1" and we would manufacture for "410" forecast for the time period "T4" (Lag3). (What would happen to the 10 units worth of Raw Material that will not be manufactured?)

When we come to T2, we will have to dispatch 500 (Lag2), so if we only made 410 in the previous step, how do we get the extra 90 units?

When we come to T3, we have to send 430 (Lag1) to stores. If we got 500 from the previous step what happens to the 70 units? If we only got 410 (as Lag3 was 410 and we assume we manufacture and send the same to the DCs), we still fall short by 20 units.

My question is at every step the forecast for a particular time period ("T4") changes whenever we move from "T0" to "T1", "T1" to "T2". So where do we get the additional units from in each stage if forecast at say Lag2 (500)> Lag3 (410) or conversely what happens to excess material if "Lag 4(420) > Lag3 (410)"

For each lag we have,

Error = (Forecast-Actuals)
Accuracy = {1-[Abs(Error)/Actuals]}

The same has been computed in the Excel file. Please let me know if my understanding is correct.

vermorel 9 months | flag | on: Display Data by Year

Envision has a today() function, see


show scalar "Today" a1b2 with today()

table X = with 
  [| today() as today |]

show table "X" a3b4 with X.today

See https://try.lokad.com/s/today-sample

In your example above, DV.today is not hard-coded but most likely loaded from the data. It's a regular variable, not the standard function today().

Hope it helps,
Joannes

ttarabbia 9 months | flag | on: Spilling to Disk in .NET [video]

Great talk - the in-memory approach makes more sense when you have a lot of global dependencies. I would imagine you get some thrashing behavior in cases where you spill the "wrong" thing.

David_BH 9 months | flag | on: ExcelFormat currency change

If you need your column to be in € when the user download the file as an Excel, you can replace
excelformat: "#,##0.00\ [$₽-419]" by
excelformat: "#,##0.00\ [$€-407]"
And for other currencies, $ => [$$-409],
¥ => [$¥-804]
₽ => [$₽-419]
£ => [$£-809]
₺ => [$₺-41F]

Thanks a lot for your contribution arkadir !
There's a slight mistake, the date format specifying should appear before the alias of the table. So the line should be this instead.

read "/example.csv" date: "yyyy-MM-dd*" as T with

s40racer 9 months | flag | on: Forecast Analysis - Forecast Quality

Now I encounter another issue. The code below follows what I posted initially.
```envision
// ///Export

quantileLow1 = 0.3
quantileLow2 = 0.05
quantileHigh1 = 0.7
quantileHigh2 = 0.95

ItemsWeek.One = dirac(1)
ItemsWeek.Demand = dirac(0)

where ItemsWeek.FutureWeekRank > 0
ItemsWeek.Demand = actionrwd.segment(
TimeIndex: ItemsWeek.FutureWeekRank
BaseLine: ItemsWeek.Baseline
Dispersion: Items.Dispersion
Alpha: 0.05
Start: dirac(ItemsWeek.FutureWeekRank - 1)
Duration: ItemsWeek.One
Samples : 1500)

// ////BackTest Demand

keep where min(ItemsWeek.Baseline) when (ItemsWeek.Baseline > 0) by Items.Sku >= 1

ItemsWeek.One=dirac(1)
ItemsWeek.BacktestForecastWeekRank = 0
where ItemsWeek.IsPast
ItemsWeek.BacktestForecastWeekRank = rank() by Items.Sku scan - ItemsWeek.Monday

keep where ItemsWeek.BacktestForecastWeekRank >0 and ItemsWeek.BacktestForecastWeekRank < 371

where ItemsWeek.BacktestForecastWeekRank > 0
ItemsWeek.BackTestDemand = actionrwd.segment(
TimeIndex: ItemsWeek.BacktestForecastWeekRank
BaseLine: ItemsWeek.Baseline
Dispersion: Items.Dispersion
Alpha: 0.05
Start: dirac(ItemsWeek.BacktestForecastWeekRank - 1)
Duration: ItemsWeek.One
Samples : 1500)
```
I have no issue with the the forward looking forecast. I have, however, issue with the backward forecast test..... specifically with BacktestForecastWeekRank. It grows to 790 days, which is greater than what actionrwd can allow (365 days). The data set I have goes back to 2018. Would this be the cause?

s40racer 9 months | flag | on: Forecast Analysis - Forecast Quality

Thank you. I resolved the issue above by using
```envision
keep where Items.Sku in ForecastProduit.Sku
```
To make sure the SKUs in the items table matches the SKU in the ForecastProduit table.

vermorel 9 months | flag | on: Forecast Analysis - Forecast Quality

I suspect its the behavior of the same aggregator when facing an empty set which defaults to zero, see my snippet below:


table Orders = with // hard-coding a table
  [| as Sku, as Date          , as Qty, as Price |] // headers
  [| "a",    date(2020, 1, 17), 5     , 1.5      |]
  [| "b",    date(2020, 2, 5) , 3     , 7.0      |]
  [| "b",    date(2020, 2, 7) , 1     , 2.0      |]
  [| "c",    date(2020, 2, 15), 7     , 5.7      |]

where Orders.Sku == "foo"
  x = same(Orders.Price) // empty set, defaults to zero
  y = same(Orders.Price) default 42 // forcing the default

show summary "same() behavior" a1b2 with
  x as "without default" // 0
  y as "with default"    // 42

Try it at https://try.lokad.com/s/same-defaults-to-zero

Hope it helps.

s40racer 9 months | flag | on: Forecast Analysis - Forecast Quality

I did an output table to see the values in the Items and Itemsweek table


today = max(Sales.Date)
todayForecast = monday(today) + 7

Items.Amount365 = sum(Sales.LokadNetAmount) when (Date >= today - 365)
Items.Q365 = sum(Sales.DeliveryQty) when (Date >= today - 365)
Items.DisplayRank = rank()  scan Items.Q365

table ItemsWeek = cross(Items, Week)
ItemsWeek.Monday = monday(ItemsWeek.Week)
ItemsWeek.IsPast = single(ForecastProduit.IsPast) by [ForecastProduit.Sku,ForecastProduit.Date] at [Items.Sku,ItemsWeek.Monday]
ItemsWeek.Baseline = single(ForecastProduit.Baseline) by [ForecastProduit.Sku,ForecastProduit.Date] at [Items.Sku,ItemsWeek.Monday]
ItemsWeek.DemandQty = single(ForecastProduit.DemandQty) by [ForecastProduit.Sku,ForecastProduit.Date] at [Items.Sku,ItemsWeek.Monday]
ItemsWeek.SmoothedDemandQty = single(ForecastProduit.SmoothedDemandQty) by [ForecastProduit.Sku,ForecastProduit.Date] at [Items.Sku,ItemsWeek.Monday]
ItemsWeek.FutureWeekRank = single(ForecastProduit.FutureWeekRank) by [ForecastProduit.Sku,ForecastProduit.Date] at [Items.Sku,ItemsWeek.Monday]
Items.Dispersion = same(ForecastProduit.Dispersion)

show table "items" with
  today
  todayForecast
  Items.Amount365 
  Items.Q365 
  Items.DisplayRank 
  ItemsWeek.Monday 
  ItemsWeek.IsPast 
  ItemsWeek.Baseline 
  ItemsWeek.DemandQty 
  ItemsWeek.SmoothedDemandQty 
  ItemsWeek.FutureWeekRank 
  Items.Dispersion 

show table "forecastproductit" with
  ForecastProduit.date
  ForecastProduit.Sku
  ForecastProduit.DemandQty
  ForecastProduit.Baseline
  ForecastProduit.Dispersion

and confirmed that there are quite a bit of data with dispersion value = 0 but this is not the case in the ForecastProduit table (as verified from the code output above). Any suggestions on what may cause the dispersion value to become 0?

The dispersion of actionrwd.foo is controlled by Dispersion:. At line 13, in your script I see:


Items.Dispersion = max(Items.AvgErrorRatio/2, 1)

This line implies that if there is 1 item (and only 1) that happens to have a super-large value, then, it will be applied for all items. This seems to be the root cause behind the high dispersion values that you are observing.

In particular,


ItemsWeek.RatioOfError = if ItemsWeek.Baseline != 0  then (ItemsWeek.Baseline - ItemsWeek.DemandQty) ^ 2 /. ItemsWeek.Baseline else 0

Above, ItemsWeek.RatioOfError can get very very large. If the baseline is small, like 0.01, and the demand qty is 1, then this value can be 100+.

Thus, my recommendations would be:

  • sanitize your ratio of error
  • don't use a max for the dispersion

Hope it helps.

Remark: I have edited your posts to add the Envision code formatting syntax, https://news.lokad.com/static/formatting

Envision is deterministic. You should not be able to re-run twice the same code over the same data and get different results.

Then, there is pseudo-randomness involved in functions like actionrwd. Thus, the seeding tend to be quite dependent on the exact fine-print of the code. If you change filters, for example, you are most likely going to end-up with different results.

Thus, even seemingly "minor" code change can lead to a re-seeding behavior.

As a rule of thumb, if the logic breaks due to re-seeding, then the logic is friable and must be adjusted so that its validity does not depend on being lucky during the seeding of the random generators.

Continuing from the previous comment - For the same SKU, the values for SeasonalityModel, Profile1, level changed between two runs on different days. I am unsure what caused the change in these values - the input data remained the same.