Packages

phoenix_kit

1.7.75
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules shop import csv_analyzer.ex
Raw

lib/modules/shop/import/csv_analyzer.ex

defmodule PhoenixKit.Modules.Shop.Import.CSVAnalyzer do
@moduledoc """
Analyze Shopify CSV files to extract option metadata.
Extracts all Option1..Option10 names and unique values from CSV
for use in the import mapping UI.
## Usage
CSVAnalyzer.analyze_options("/path/to/products.csv")
# => %{
# options: [
# %{name: "Size", position: 1, values: ["Small", "Medium", "Large"]},
# %{name: "Color", position: 2, values: ["Red", "Blue", "Green"]}
# ],
# total_products: 150,
# total_variants: 450
# }
"""
alias PhoenixKit.Modules.Shop.Import.CSVParser
alias PhoenixKit.Modules.Shop.Import.Filter
@max_options 10
@doc """
Analyzes a CSV file and extracts option metadata.
Returns a map with:
- `options` - List of option definitions with name, position, and unique values
- `total_products` - Number of unique product handles
- `total_variants` - Total number of variant rows
## Examples
CSVAnalyzer.analyze_options("/tmp/products.csv")
# => %{
# options: [
# %{name: "Size", position: 1, values: ["S", "M", "L", "XL"]},
# %{name: "Cup Color", position: 2, values: ["Red", "Blue"]},
# %{name: "Liquid Color", position: 3, values: ["Clear", "Amber"]}
# ],
# total_products: 50,
# total_variants: 200
# }
"""
def analyze_options(file_path, config \\ nil) do
grouped = CSVParser.parse_and_group(file_path)
# Apply import config filter if provided and not skipped
{filtered, skipped_count} =
if config && !config.skip_filter do
filtered =
grouped
|> Enum.filter(fn {_handle, rows} -> Filter.should_include?(rows, config) end)
|> Map.new()
{filtered, map_size(grouped) - map_size(filtered)}
else
{grouped, 0}
end
# Group options by NAME instead of position
# This handles cases where different products use Option1 for different purposes
{option_data, total_variants} =
Enum.reduce(filtered, {%{}, 0}, fn {_handle, rows}, {acc, variant_count} ->
# Get option names and values from all rows
first_row = List.first(rows)
variant_rows = Enum.filter(rows, &has_price?/1)
# Collect options by name
acc = collect_options_by_name(acc, first_row, variant_rows)
{acc, variant_count + length(variant_rows)}
end)
# Convert to output format, sorted by name
options =
option_data
|> Enum.sort_by(fn {name, _} -> String.downcase(name) end)
|> Enum.with_index(1)
|> Enum.map(fn {{name, values}, index} ->
%{
name: name,
position: index,
values: MapSet.to_list(values) |> Enum.sort()
}
end)
%{
options: options,
total_products: map_size(filtered),
total_variants: total_variants,
total_skipped: skipped_count
}
end
@doc """
Quick analysis - only extracts option names without values.
Faster than full analysis, useful for initial UI display.
"""
def analyze_option_names(file_path) do
# Read just the first few rows to get option names
grouped = CSVParser.parse_and_group(file_path)
# Get first product's first row
first_product_rows = grouped |> Map.values() |> List.first() || []
first_row = List.first(first_product_rows) || %{}
# Extract option names
for i <- 1..@max_options,
name = get_option_name(first_row, i),
name != nil do
%{name: name, position: i}
end
end
@doc """
Compares CSV option values with global option values.
Returns a map showing which values are new (not in global option).
## Examples
CSVAnalyzer.compare_with_global_option(csv_values, global_option)
# => %{
# existing: ["Red", "Blue"],
# new: ["Yellow", "Purple"]
# }
"""
def compare_with_global_option(csv_values, global_option) when is_list(csv_values) do
global_values = extract_global_option_values(global_option)
global_set = MapSet.new(global_values)
csv_set = MapSet.new(csv_values)
existing = MapSet.intersection(csv_set, global_set) |> MapSet.to_list()
new_values = MapSet.difference(csv_set, global_set) |> MapSet.to_list()
%{
existing: Enum.sort(existing),
new: Enum.sort(new_values)
}
end
def compare_with_global_option(_, _), do: %{existing: [], new: []}
# Extract values from global option (handles both simple and enhanced format)
defp extract_global_option_values(nil), do: []
defp extract_global_option_values(%{"options" => options}) when is_list(options) do
Enum.map(options, fn
opt when is_binary(opt) -> opt
%{"value" => value} -> value
_ -> nil
end)
|> Enum.reject(&is_nil/1)
end
defp extract_global_option_values(_), do: []
# Private helpers
# Collect options grouped by name (not position)
defp collect_options_by_name(acc, first_row, variant_rows) do
# Get option names from first row
option_names =
for i <- 1..@max_options,
name = get_option_name(first_row, i),
name != nil,
do: {i, name}
# Collect values for each option name
Enum.reduce(option_names, acc, fn {position, name}, acc ->
# Get all values for this option from variant rows
values =
Enum.reduce(variant_rows, MapSet.new(), fn row, values_acc ->
case get_option_value(row, position) do
nil -> values_acc
"" -> values_acc
value -> MapSet.put(values_acc, value)
end
end)
# Merge with existing values for this option name
existing = Map.get(acc, name, MapSet.new())
Map.put(acc, name, MapSet.union(existing, values))
end)
end
defp get_option_name(row, position) do
key = "Option#{position} Name"
case row[key] do
nil -> nil
"" -> nil
name -> String.trim(name)
end
end
defp get_option_value(row, position) do
key = "Option#{position} Value"
case row[key] do
nil -> nil
"" -> nil
value -> String.trim(value)
end
end
defp has_price?(row) do
price = row["Variant Price"]
price != nil and price != ""
end
end