Search
Write a publication
Pull to refresh

Comments 5

А 1с то здесь зачем? Оно не выполняет ровным счетом ничего, закиньте также эти файлы в условный гигачат, и будет то же самое, если он умеет файлы читать)

У гигачат нет code interpreter

Я так понял, что она должна была написать код, потом показать результат его выполнения. Можно посмотреть код, который был создан в данном случае ?

import pandas as pd
from pathlib import Path
from collections import defaultdict, deque

def read_csv_smart(path):
    for enc in ["utf-8", "utf-8-sig", "cp1251", "latin1"]:
        try:
            return pd.read_csv(path, encoding=enc)
        except Exception:
            continue
    # fallback
    return pd.read_csv(path, encoding="latin1", errors="ignore")

# Load
purchases = read_csv_smart("/mnt/data/file-7hqUPaq8LxdUaPjygcVFrh-purchases.csv")
sales = read_csv_smart("/mnt/data/file-Q1Zn1817nakCUSQTtTxs25-sales.csv")

# Normalize column names
purchases.columns = [c.strip().lower() for c in purchases.columns]
sales.columns = [c.strip().lower() for c in sales.columns]

# Map Russian columns
# дата, товар, количество, сумма
req_cols = ["дата", "товар", "количество", "сумма"]
for df_name, df in [("Закупки", purchases), ("Продажи", sales)]:
    missing = [c for c in req_cols if c not in df.columns]
    if missing:
        raise ValueError(f"{df_name}: отсутствуют столбцы {missing}. Найдены: {list(df.columns)}")

# Prepare dataframes
p_df = purchases[["дата", "товар", "количество", "сумма"]].copy()
s_df = sales[["дата", "товар", "количество", "сумма"]].copy()

# Parse dates (dd.mm.yy)
for df in (p_df, s_df):
    df["дата"] = pd.to_datetime(df["дата"], format="%d.%m.%y", errors="coerce")
    df["количество"] = pd.to_numeric(df["количество"], errors="coerce")
    df["сумма"] = pd.to_numeric(df["сумма"], errors="coerce")

# Drop bad rows
p_df = p_df.dropna(subset=["дата", "товар", "количество", "сумма"])
s_df = s_df.dropna(subset=["дата", "товар", "количество", "сумма"])

# Compute unit cost for purchases
p_df = p_df[p_df["количество"] > 0]
p_df["unit_cost"] = p_df["сумма"] / p_df["количество"]

# For sales, revenue is given as total "сумма"
s_df = s_df[s_df["количество"] > 0]
s_df["revenue"] = s_df["сумма"]

# Sort
p_df = p_df.sort_values(["товар", "дата"]).reset_index(drop=True)
s_df = s_df.sort_values(["товар", "дата"]).reset_index(drop=True)

# Build FIFO queues per product
purchase_queues = defaultdict(deque)
for prod, grp in p_df.groupby("товар", sort=False):
    for _, r in grp.iterrows():
        purchase_queues[prod].append([float(r["количество"]), float(r["unit_cost"]), r["дата"]])

# Process sales
rows = []
shortages = defaultdict(float)

for prod, grp in s_df.groupby("товар", sort=False):
    for _, r in grp.iterrows():
        qty = float(r["количество"])
        revenue = float(r["revenue"])
        remaining = qty
        cogs = 0.0
        pq = purchase_queues[prod]
        while remaining > 0 and pq:
            lot_qty, lot_cost, lot_date = pq[0]
            use = min(remaining, lot_qty)
            cogs += use * lot_cost
            lot_qty -= use
            remaining -= use
            if lot_qty == 0:
                pq.popleft()
            else:
                pq[0][0] = lot_qty
        if remaining > 0:
            shortages[prod] += remaining
        rows.append({
            "date": r["дата"],
            "product": prod,
            "qty_sold": qty,
            "revenue": revenue,
            "cogs_fifo_known": cogs,
            "gross_profit_known": revenue - cogs,
            "uncosted_qty_due_to_shortage": max(0.0, remaining)
        })

res_df = pd.DataFrame(rows)

# Aggregate per product
agg = res_df.groupby("product", as_index=False).agg(
    revenue=("revenue", "sum"),
    cogs_fifo_known=("cogs_fifo_known", "sum"),
    gross_profit=("gross_profit_known", "sum"),
    qty_sold=("qty_sold", "sum"),
    uncosted_qty=("uncosted_qty_due_to_shortage", "sum")
).sort_values("gross_profit", ascending=False)

# Display results
display(agg)

# Totals
totals = pd.DataFrame({
    "product": ["ИТОГО"],
    "revenue": [agg["revenue"].sum()],
    "cogs_fifo_known": [agg["cogs_fifo_known"].sum()],
    "gross_profit": [agg["gross_profit"].sum()],
    "qty_sold": [agg["qty_sold"].sum()],
    "uncosted_qty": [agg["uncosted_qty"].sum()],
})
display(totals)

# If shortages, show brief note
if (agg["uncosted_qty"] > 0).any():
    display({"Примечание": "Обнаружены продажи без достаточного запаса на момент продажи. COGS рассчитан только для доступной части. Проверьте последовательность дат закупок/продаж."})

Зря Вы 1С выбрали в статье в качестве среды для написания запросов к LLM. Это приводит к тому, что смотришь на результат с точки зрения применения 1С, а тут нужно чтобы бы просто работало.

Может быть с точки зрения ИТ консультанта, продвигающего LLM тут все хорошо, типа смотрите, написал одно предлжение и все сделано. Но с мой точки зрения написанное работать не будет, и получается что основная задача не выполнена.

Проблемы (писал как я вижу выполнение этого кода, возможно в чем-то ошибся):

  1. Себестоимость рассчитана делением суммы закупки на количество. Это приведет к ошибкам - будут не сходиться копейки. Пример: Поступили 3 товара на сумму 100 руб. Для равномерного списания себестоимости необходимо по каждой входящей партии учитывать остаток не только количества, но и суммы. Расчет себестоимости проводить на момент списания. (Решение через систему линейных уравнений, применяемое в ERP, я тут не рассматриваю).

  2. Не учтен "момент времени". Данные отсортированы просто по дате. Но в одну дату может быть несколько документов. Для того, чтобы гарантировать их порядок в пределах даты, необходимо добавить еще одну колонку (например порядковый номер) и применять сортировку сначала по дате, затем по порядковому номеру.

  3. При списании не учитывается дата поступления. То есть она без ограничения списывает партии из будущих поставок, а так не должно быть. Поступление должно быть не позже этой же даты или конца месяца.

  4. Нет поддержки возвратов. То есть записей с отрицательным количеством.

Мелочи типа правильно добавить округления, try без обработки except, то что нельзя сравнивать float равно 0 я даже не рассматриваю.

Реально можно взять готовый пример импорта данных для pandas, изменить в нем названия и типы колонок и получить во входных таблицах тоже самое. С примерно теми же затратами времени.
А что касается основного алгоритма то проще писать его самому чем пытаться адаптировать то что здесь написано. Потому что хорошо, когда есть явная ошибка. А при исправлении чужого кода легко получить мелкую неявную ошибку. Которая допущена в январе, но не проявится сразу, а проявится в декабре. И придется объяснять руководству, что нам надо открывать период и перезакрывать все месяца с начала года, с пересдачей отчетности.

Sign up to leave a comment.

Articles