Files
query-orchestration/internal/document/text/pageText.go
T

227 lines
5.8 KiB
Go
Raw Normal View History

package documenttext
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
documenttypes "queryorchestration/internal/document/types"
"github.com/aws/aws-sdk-go-v2/service/textract"
"github.com/aws/aws-sdk-go-v2/service/textract/types"
"github.com/google/uuid"
)
func (s *Service) getPageText(ctx context.Context, document documenttypes.File, index int) (string, error) {
res, err := s.GetBasePage(ctx, document, index)
if err != nil {
return "", err
}
features, err := s.ListRequiredFeatures(ctx, res.Blocks, index)
if err != nil {
return "", err
}
blocks := res.Blocks
if len(features) > 0 {
res, err := s.GetPageWithFeatures(ctx, document, index, features)
if err != nil {
return "", err
}
blocks = res.Blocks
}
elements, err := s.getPageElements(ctx, document, index, blocks)
if err != nil {
return "", err
}
return s.buildPageText(ctx, elements, index)
}
type PageElements struct {
Page uuid.UUID
BlockMap map[uuid.UUID]types.Block
DirectChildren []uuid.UUID
Tables []uuid.UUID
}
2025-04-23 17:51:44 +00:00
func (s *Service) getPageElements(ctx context.Context, document documenttypes.File, index int, blocks []types.Block) (PageElements, error) {
elements := PageElements{}
pageBlock, err := s.GetPageBlock(blocks)
if err != nil {
return PageElements{}, err
}
elements.Page = pageBlock
elements.BlockMap = s.GetBlockMap(blocks)
elements.DirectChildren = s.GetDirectChildren(pageBlock, elements.BlockMap)
for _, block := range blocks {
if block.BlockType != types.BlockTypeTable {
continue
}
id, err := uuid.Parse(*block.Id)
if err != nil {
return PageElements{}, err
}
elements.Tables = append(elements.Tables, id)
}
return elements, nil
}
func (s *Service) ListRequiredFeatures(ctx context.Context, blocks []types.Block, index int) ([]types.FeatureType, error) {
features := []types.FeatureType{}
for _, block := range blocks {
switch block.BlockType {
case types.BlockTypeLayoutTable:
features = append(features, types.FeatureTypeTables)
case types.BlockTypeLayoutKeyValue:
features = append(features, types.FeatureTypeForms)
}
}
if len(features) > 0 {
features = append(features, types.FeatureTypeLayout)
features = append(features, types.FeatureTypeSignatures)
}
return features, nil
}
type buildParams struct {
tableIndex int
}
func (s *Service) buildPageText(ctx context.Context, elements PageElements, index int) (string, error) {
var builder strings.Builder
params := buildParams{}
for _, id := range elements.DirectChildren {
err := s.addComponentText(ctx, id, elements, &params, &builder)
if err != nil {
return "", err
}
}
for params.tableIndex < len(elements.Tables) {
err := s.addTable(ctx, elements.Tables[params.tableIndex], elements.BlockMap, &builder)
2025-04-25 17:02:50 +00:00
if err != nil {
return "", err
}
params.tableIndex++
2025-04-25 17:02:50 +00:00
}
numOfSignatures := 0
for _, block := range elements.BlockMap {
if block.BlockType == types.BlockTypeSignature {
numOfSignatures++
}
}
if numOfSignatures > 0 {
builder.WriteString(fmt.Sprintf("\nThis page has %d signature.\n", numOfSignatures))
}
return builder.String(), nil
}
func (s *Service) addComponentText(ctx context.Context, id uuid.UUID, elements PageElements, params *buildParams, builder *strings.Builder) error {
block := elements.BlockMap[id]
switch block.BlockType {
case types.BlockTypeLayoutText, types.BlockTypeLayoutTitle, types.BlockTypeLayoutHeader, types.BlockTypeLayoutFooter, types.BlockTypeLayoutSectionHeader, types.BlockTypeLayoutPageNumber, types.BlockTypeLayoutList, types.BlockTypeLayoutFigure:
for _, id := range s.getRelationshipIds(block) {
err := s.addComponentText(ctx, id, elements, params, builder)
if err != nil {
return err
}
}
case types.BlockTypeLine:
_, err := builder.WriteString(fmt.Sprintf("%s\n", *block.Text))
if err != nil {
slog.Error("unable to write string", "error", err)
return err
}
case types.BlockTypeWord:
_, err := builder.WriteString(fmt.Sprintf(" %s", *block.Text))
if err != nil {
slog.Error("unable to write string", "error", err)
return err
}
case types.BlockTypeLayoutTable:
if params.tableIndex < len(elements.Tables) {
err := s.addTable(ctx, elements.Tables[params.tableIndex], elements.BlockMap, builder)
if err != nil {
return err
}
} else {
for _, child := range s.getRelationshipIds(block) {
err := s.addComponentText(ctx, child, elements, params, builder)
2025-04-23 17:51:44 +00:00
if err != nil {
return err
}
}
}
params.tableIndex++
case types.BlockTypeLayoutKeyValue:
err := s.addKeyValue(id, elements, builder)
if err != nil {
return err
}
2025-04-23 17:51:44 +00:00
default:
slog.Debug("uncaught component", "type", block.BlockType)
}
return nil
}
func (s *Service) GetBasePage(ctx context.Context, doc documenttypes.File, index int) (textract.AnalyzeDocumentOutput, error) {
return s.GetPageWithFeatures(ctx, doc, index, []types.FeatureType{
types.FeatureTypeLayout,
types.FeatureTypeSignatures,
})
}
func (s *Service) GetPageWithFeatures(ctx context.Context, doc documenttypes.File, index int, features []types.FeatureType) (textract.AnalyzeDocumentOutput, error) {
pageBytes, err := doc.GetPageAsPNG(ctx, index)
if err != nil {
return textract.AnalyzeDocumentOutput{}, err
}
res, err := s.cfg.GetTextractClient().AnalyzeDocument(ctx, &textract.AnalyzeDocumentInput{
Document: &types.Document{
Bytes: pageBytes,
},
FeatureTypes: features,
})
if err != nil {
return textract.AnalyzeDocumentOutput{}, err
}
return *res, err
}
func (s *Service) GetPageBlock(blocks []types.Block) (uuid.UUID, error) {
var pageBlock *types.Block
for _, block := range blocks {
if block.BlockType == types.BlockTypePage {
pageBlock = &block
break
}
}
if pageBlock == nil {
return uuid.Nil, errors.New("no page block found")
}
return uuid.Parse(*pageBlock.Id)
}